blob: 1cb88fff6068eff1125dcabae6f4e70af9365026 [file] [log] [blame]
// Copyright 2018 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 "cxx_compiler_info_builder.h"
#include "absl/strings/str_split.h"
#include "cmdline_parser.h"
#include "compiler_info.h"
#include "counterz.h"
#include "glog/logging.h"
#include "glog/stl_logging.h"
#include "goma_hash.h"
#include "ioutil.h"
#include "path.h"
#include "scoped_tmp_file.h"
#include "util.h"
namespace devtools_goma {
/* static */
void CxxCompilerInfoBuilder::ParseGetSubprogramsOutput(
const string& gcc_output,
std::vector<string>* paths) {
const std::vector<string> candidates = {"as", "objcopy", "cc1",
"cc1plus", "cpp", "nm"};
std::set<string> known;
for (auto&& line :
absl::StrSplit(gcc_output, absl::ByAnyChar("\r\n"), absl::SkipEmpty())) {
if (!absl::StartsWith(line, " ")) {
continue;
}
std::vector<string> argv;
// Since clang is not used on Windows now, this won't be the issue.
ParsePosixCommandLineToArgv(line, &argv);
if (argv.empty()) {
continue;
}
const string& cmd = argv[0];
absl::string_view basename = file::Basename(cmd);
if (basename == cmd) {
// To keep backword compatibility, we do not add subprogram searched
// in PATH.
LOG(INFO) << "ignore subprogram searched in PATH."
<< " cmd=" << cmd;
continue;
}
if (!known.insert(cmd).second) {
continue;
}
for (const auto& candidate : candidates) {
if (basename == candidate || absl::EndsWith(basename, "-" + candidate)) {
paths->push_back(cmd);
break;
}
}
}
}
/* static */
bool CxxCompilerInfoBuilder::GetSubprograms(
const string& gcc_path,
const string& lang,
const std::vector<string>& compiler_info_flags,
const std::vector<string>& compiler_info_envs,
const string& cwd,
bool warn_on_empty,
std::vector<string>* subprogs) {
std::vector<string> argv = {gcc_path};
copy(compiler_info_flags.begin(), compiler_info_flags.end(),
back_inserter(argv));
// Since a compiler returns EXIT_FAILURE if fails to output file,
// we need to use a fake temporary file.
// Failure of writing *.dwo might be the reason.
ScopedTmpDir tmp("get_subprograms");
if (!tmp.valid()) {
LOG(ERROR) << "cannot make an empty directory";
return false;
}
#ifdef _WIN32
// This code is used by NaCl gcc, PNaCl clang on Windows.
// Former uses /dev/null as null device, and latter recently uses NUL as
// null device. To provide the same code to both, let me use temporary
// file for that.
ScopedTmpFile tmpfile("get_subprograms");
if (!tmpfile.valid()) {
LOG(ERROR) << "cannot make an empty file";
return false;
}
tmpfile.Close();
const string& empty_file = tmpfile.filename();
VLOG(2) << "empty_file=" << empty_file;
#else
const string& empty_file = "/dev/null";
#endif
const string output_file = file::JoinPath(tmp.dirname(), "output");
VLOG(2) << "output_file=" << output_file;
argv.emplace_back("-x" + lang);
argv.emplace_back("-c");
argv.emplace_back(empty_file);
argv.emplace_back("-o");
argv.emplace_back(output_file);
argv.emplace_back("-v");
int32_t status;
string gcc_output;
{
GOMA_COUNTERZ("ReadCommandOutput(subprogram)");
gcc_output = ReadCommandOutput(gcc_path, argv, compiler_info_envs, cwd,
MERGE_STDOUT_STDERR, &status);
}
if (status != 0) {
LOG(ERROR) << "ReadCommandOutput exited with non zero status code."
<< " gcc_path=" << gcc_path << " status=" << status
<< " argv=" << argv << " env=" << compiler_info_envs
<< " cwd=" << cwd << " gcc_output=" << gcc_output;
return false;
}
VLOG(1) << "GetSubprograms:"
<< " gcc_path=" << gcc_path << " status=" << status
<< " argv=" << argv << " env=" << compiler_info_envs << " cwd=" << cwd
<< " gcc_output=" << gcc_output;
ParseGetSubprogramsOutput(gcc_output, subprogs);
LOG_IF(ERROR, warn_on_empty && subprogs->empty())
<< "Expect to have at least one subprograms but empty."
<< " gcc_path=" << gcc_path << " status=" << status << " argv=" << argv
<< " env=" << compiler_info_envs << " cwd=" << cwd
<< " gcc_output=" << gcc_output;
return true;
}
/* static */
string CxxCompilerInfoBuilder::GetRealSubprogramPath(
const string& subprog_path) {
#ifndef __linux__
return subprog_path;
#else
// Currently, we only see objcopy runs via shell script wrapper, and
// nothing else (i.e. no as or so). (b/30571185)
if (file::Basename(subprog_path) != "objcopy") {
return subprog_path;
}
// Assume ChromeOS objcopy is always in
// "<target arch>/binutils-bin/<version>-gold/objcopy",
// and real objcopy is in
// "<target arch>/binutils-bin/<version>/objcopy.elf".
if (file::Basename(file::Dirname(file::Dirname(subprog_path))) !=
"binutils-bin") {
return subprog_path;
}
absl::string_view dirname = file::Dirname(subprog_path);
static const char kGoldSuffix[] = "-gold";
if (absl::EndsWith(dirname, kGoldSuffix)) {
dirname.remove_suffix(sizeof(kGoldSuffix) - 1);
}
const string new_subprog_path = file::JoinPath(dirname, "objcopy.elf");
FileStat new_id(new_subprog_path);
if (!new_id.IsValid()) {
LOG(INFO) << ".elf does not exist, might not be chromeos path?"
<< " expect to exist=" << new_subprog_path
<< " orignal subprog_path=" << subprog_path;
return subprog_path;
}
LOG(INFO) << "Hack for objcopy used for ChromeOS simple chrome build:"
<< " apparent subprog_path=" << subprog_path
<< " real subprog_path=" << new_subprog_path;
return new_subprog_path;
#endif
}
/* static */
bool CxxCompilerInfoBuilder::SubprogramInfoFromPath(
const string& user_specified_path,
const string& abs_path,
CompilerInfoData::SubprogramInfo* s) {
FileStat file_stat(abs_path);
if (!file_stat.IsValid()) {
return false;
}
string hash;
if (!GomaSha256FromFile(GetRealSubprogramPath(abs_path), &hash)) {
return false;
}
s->set_abs_path(abs_path);
s->set_user_specified_path(user_specified_path);
s->set_hash(hash);
SetFileStatToData(file_stat, s->mutable_file_stat());
return true;
}
void CxxCompilerInfoBuilder::SetLanguageExtension(
CompilerInfoData* data) const {
(void)data->mutable_cxx();
}
} // namespace devtools_goma