blob: 856750139d682443c11aaa304d6c004c795389d7 [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 "chromeos_compiler_info_builder_helper.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "absl/strings/strip.h"
#include "elf_util.h"
#include "glog/logging.h"
#include "lib/file_helper.h"
#include "path.h"
#include <unistd.h>
namespace devtools_goma {
namespace {
constexpr absl::string_view kClang = "/usr/bin/clang";
constexpr absl::string_view kClangxx = "/usr/bin/clang++";
bool IsClangWrapperInChroot(const absl::string_view local_compiler_path) {
if (file::Dirname(local_compiler_path) != "/usr/bin") {
return false;
}
absl::string_view basename = file::Basename(local_compiler_path);
return absl::EndsWith(basename, "-clang") ||
absl::EndsWith(basename, "-clang++");
}
bool IsKnownClangInChroot(const absl::string_view local_compiler_path) {
return local_compiler_path == kClang || local_compiler_path == kClangxx ||
IsClangWrapperInChroot(local_compiler_path);
}
bool ParseEnvdPath(absl::string_view envd_path, std::string* path) {
// content is like
//
// ```
// PATH="/usr/x86_64-pc-linux-gnu/x86_64-cros-linux-gnu/gcc-bin/4.9.x"
// ROOTPATH="/usr/x86_64-pc-linux-gnu/x86_64-cros-linux-gnu/gcc-bin/4.9.x"
// ```
std::string content;
if (!ReadFileToString(envd_path, &content)) {
LOG(ERROR) << "failed to open/read " << envd_path;
return false;
}
for (absl::string_view line :
absl::StrSplit(content, absl::ByAnyChar("\r\n"), absl::SkipEmpty())) {
if (absl::ConsumePrefix(&line, "PATH=\"") &&
absl::ConsumeSuffix(&line, "\"")) {
*path = std::string(line);
return true;
}
}
return false;
}
} // anonymous namespace
// static
bool ChromeOSCompilerInfoBuilderHelper::IsSimpleChromeClangCommand(
absl::string_view local_compiler_path,
absl::string_view real_compiler_path) {
if (!(absl::EndsWith(local_compiler_path, "clang") ||
absl::EndsWith(local_compiler_path, "clang++"))) {
return false;
}
if (!absl::EndsWith(real_compiler_path, ".elf")) {
return false;
}
return true;
}
// static
bool ChromeOSCompilerInfoBuilderHelper::CollectSimpleChromeClangResources(
const std::string& cwd,
absl::string_view local_compiler_path,
absl::string_view real_compiler_path,
std::vector<std::string>* resource_paths) {
absl::string_view local_compiler_dir = file::Dirname(local_compiler_path);
int version;
if (!EstimateClangMajorVersion(real_compiler_path, &version)) {
LOG(ERROR) << "failed to estimate clang major version"
<< " real_compiler_path=" << real_compiler_path;
return false;
}
// if local_compiler is clang++, real_compiler is clang-<N>.elf.
// However, clang++-<N>.elf and clang-<N> are both necessary to run clang++.
if (absl::EndsWith(local_compiler_path, "clang++")) {
resource_paths->push_back(file::JoinPath(
local_compiler_dir, absl::StrCat("clang++-", version, ".elf")));
resource_paths->push_back(
file::JoinPath(local_compiler_dir, absl::StrCat("clang-", version)));
}
// Please see --library-path argument in simple Chrome's clang wrapper.
const std::vector<std::string> search_paths = {
file::JoinPath(local_compiler_dir, "..", "..", "lib"),
file::JoinPath(local_compiler_dir, "..", "lib64"),
};
// TODO: use relative path real_compiler_path and simplify.
// Since real_compiler_path is absolute path, we cannot use that here.
const std::string compiler_path = file::JoinPath(
local_compiler_dir, absl::StrCat("clang-", version, ".elf"));
// Since the shell script wrapper has --inhibit-rpath '',
// we should ignore RPATH and RUNPATH specified in ELF.
ElfDepParser edp(cwd, search_paths, true);
absl::flat_hash_set<std::string> deps;
if (!edp.GetDeps(compiler_path, &deps)) {
LOG(ERROR) << "failed to get library dependencies."
<< " cwd=" << cwd
<< " local_compiler_path=" << local_compiler_path
<< " real_compiler_path=" << real_compiler_path;
return false;
}
for (const auto& path : deps) {
resource_paths->push_back(path);
}
return true;
}
// static
bool ChromeOSCompilerInfoBuilderHelper::EstimateClangMajorVersion(
absl::string_view real_compiler_path,
int* version) {
// Assuming real_compiler_path filename is like
// `clang-<N>.elf` or `clang-<N>`.
absl::string_view filename = file::Basename(real_compiler_path);
if (!absl::ConsumePrefix(&filename, "clang-")) {
LOG(INFO) << "not start with clang-:" << filename;
return false;
}
// If this has .elf, remove that.
// If it doesn't exist, it's not an error.
absl::ConsumeSuffix(&filename, ".elf");
if (!absl::SimpleAtoi(filename, version)) {
LOG(INFO) << "not an integer:" << filename;
return false;
}
return true;
}
// static
bool ChromeOSCompilerInfoBuilderHelper::IsClangInChrootEnv(
absl::string_view local_compiler_path) {
if (!IsKnownClangInChroot(local_compiler_path)) {
return false;
}
// chromeos chroot env should have /etc/cros_chroot_version.
if (access("/etc/cros_chroot_version", F_OK) < 0) {
return false;
}
return true;
}
namespace {
bool SetChrootClangResourcePaths(const std::string& cwd,
const std::vector<std::string>& files,
absl::string_view local_compiler_path,
absl::string_view real_compiler_path,
std::vector<std::string>* resource_paths) {
constexpr absl::string_view kLdSoConfPath = "/etc/ld.so.conf";
std::string content;
if (!ReadFileToString(kLdSoConfPath, &content)) {
LOG(ERROR) << "failed to open/read " << kLdSoConfPath;
return false;
}
std::vector<std::string> searchpath = ParseLdSoConf(content);
ElfDepParser edp(cwd, searchpath, false);
absl::flat_hash_set<std::string> exec_deps;
for (const auto& file : files) {
if (file != local_compiler_path && file != real_compiler_path) {
resource_paths->push_back(file);
}
if (!IsElfFile(file)) {
continue;
}
if (!edp.GetDeps(file, &exec_deps)) {
LOG(ERROR) << "failed to get library dependencies for executable."
<< " file=" << file << " cwd=" << cwd;
return false;
}
}
for (const auto& path : exec_deps) {
resource_paths->push_back(path);
}
return true;
}
} // namespace
// static
bool ChromeOSCompilerInfoBuilderHelper::CollectChrootClangResources(
const std::string& cwd,
absl::string_view local_compiler_path,
absl::string_view real_compiler_path,
std::vector<std::string>* resource_paths) {
std::vector<std::string> resources = {
std::string(local_compiler_path),
std::string(real_compiler_path),
};
if (!IsClangWrapperInChroot(local_compiler_path)) {
return SetChrootClangResourcePaths(cwd, resources, local_compiler_path,
real_compiler_path, resource_paths);
}
//
// Code below list up files needed to run the wrapper.
//
if (IsElfFile(std::string(local_compiler_path))) {
// Assuming |local_compiler_path| is a program to detect a position of
// the wrapper, and execute.
// Then, we need to upload files to decide wrapper positions (.NATIVE
// and 05gcc-*), and the wrapper script itself.
resources.emplace_back("/etc/env.d/gcc/.NATIVE");
absl::string_view compile_target = file::Stem(local_compiler_path);
if (!absl::ConsumeSuffix(&compile_target, "-clang") &&
!absl::ConsumeSuffix(&compile_target, "-clang++")) {
PLOG(ERROR) << "compiler name seems not be expected."
<< " local_compiler_path=" << local_compiler_path;
return false;
}
const std::string envfilename =
absl::StrCat("/etc/env.d/05gcc-", compile_target);
if (access(envfilename.c_str(), R_OK) != 0) {
LOG(ERROR) << "env file not found."
<< " envfilename=" << envfilename
<< " local_compiler_path=" << local_compiler_path
<< " real_compiler_path=" << real_compiler_path;
return false;
}
resources.push_back(envfilename);
std::string path_from_envd;
if (!ParseEnvdPath(envfilename, &path_from_envd)) {
LOG(ERROR) << "Failed to parse env file."
<< " envfilename=" << envfilename
<< " local_compiler_path=" << local_compiler_path
<< " real_compiler_path=" << real_compiler_path;
return false;
}
// Even if <basename> ends with clang++, we also need clang ones.
absl::string_view base_compiler_path = file::Basename(local_compiler_path);
resources.push_back(file::JoinPath(path_from_envd, base_compiler_path));
if (absl::EndsWith(base_compiler_path, "clang++")) {
resources.push_back(file::JoinPath(
path_from_envd, absl::StripSuffix(base_compiler_path, "++")));
}
}
// Actually /usr/bin/clang{,++} is called from the wrapper.
if (absl::EndsWith(local_compiler_path, "clang++")) {
resources.emplace_back(kClangxx);
} else {
resources.emplace_back(kClang);
}
// We also need python2 for wrapper.
// TODO: better way to handle python library usage change.
std::vector<std::string> python2_deps = {
"/usr/lib64/python2.7/_abcoll.py",
"/usr/lib64/python2.7/abc.py",
"/usr/lib64/python2.7/codecs.py",
"/usr/lib64/python2.7/copy_reg.py",
"/usr/lib64/python2.7/encodings/aliases.py",
"/usr/lib64/python2.7/encodings/__init__.py",
"/usr/lib64/python2.7/encodings/utf_8.py",
"/usr/lib64/python2.7/__future__.py",
"/usr/lib64/python2.7/genericpath.py",
"/usr/lib64/python2.7/linecache.py",
"/usr/lib64/python2.7/os.py",
"/usr/lib64/python2.7/posixpath.py",
"/usr/lib64/python2.7/stat.py",
"/usr/lib64/python2.7/types.py",
"/usr/lib64/python2.7/UserDict.py",
"/usr/lib64/python2.7/warnings.py",
"/usr/lib64/python2.7/_weakrefset.py",
// "lib-dynload" is needed for detecting EXEC_PREFIX in python.
// The following code make it created in remote.
"/usr/lib64/python2.7/lib-dynload/../../../bin/python2",
};
resources.insert(resources.end(),
std::make_move_iterator(python2_deps.begin()),
std::make_move_iterator(python2_deps.end()));
return SetChrootClangResourcePaths(cwd, resources, local_compiler_path,
real_compiler_path, resource_paths);
}
// static
void ChromeOSCompilerInfoBuilderHelper::SetAdditionalFlags(
absl::string_view local_compiler_path,
google::protobuf::RepeatedPtrField<std::string>* additional_flags) {
if (IsClangWrapperInChroot(local_compiler_path)) {
// Wrapper tries to set up ccache, but it's meaningless in goma.
// we have to set -noccache.
additional_flags->Add("-noccache");
}
}
} // namespace devtools_goma