blob: da3a7aa06e10797981b6463ef905b5c91f3308b5 [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 "vc_compiler_info_builder.h"
#include "clang_compiler_info_builder_helper.h"
#include "cmdline_parser.h"
#include "counterz.h"
#include "glog/logging.h"
#include "glog/stl_logging.h"
#include "mypath.h"
#include "path.h"
#include "util.h"
#include "vc_flags.h"
namespace devtools_goma {
namespace {
string GetVCOutputString(const string& cl_exe_path,
const string& vcflags,
const string& dumb_file,
const std::vector<string>& compiler_info_flags,
const std::vector<string>& compiler_info_envs,
const string& cwd) {
// The trick we do here gives both include path and predefined macros.
std::vector<string> argv;
argv.push_back(cl_exe_path);
argv.push_back("/nologo");
argv.push_back(vcflags);
copy(compiler_info_flags.begin(), compiler_info_flags.end(),
back_inserter(argv));
argv.push_back(dumb_file);
int32_t dummy; // It is fine to return non zero status code.
{
GOMA_COUNTERZ("ReadCommandOutput(/nologo)");
return ReadCommandOutput(cl_exe_path, argv, compiler_info_envs, cwd,
MERGE_STDOUT_STDERR, &dummy);
}
}
// Since clang-cl is emulation of cl.exe, it might not have meaningful
// clang-cl -dumpversion. It leads inconsistency of goma's compiler version
// format between clang and clang-cl. Former expect <dumpversion>[<version>]
// latter cannot have <dumpversion>.
// As a result, let me use different way of getting version string.
// TODO: make this support gcc and use this instead of
// GetGccTarget.
string GetClangClSharpOutput(const string& clang_path,
const std::vector<string>& compiler_info_flags,
const std::vector<string>& compiler_info_envs,
const string& cwd) {
std::vector<string> argv;
argv.push_back(clang_path);
copy(compiler_info_flags.begin(), compiler_info_flags.end(),
back_inserter(argv));
argv.push_back("-###");
int32_t status = 0;
string output;
{
GOMA_COUNTERZ("ReadCommandOutput(###)");
output = ReadCommandOutput(clang_path, argv, compiler_info_envs, cwd,
MERGE_STDOUT_STDERR, &status);
}
if (status != 0) {
LOG(ERROR) << "ReadCommandOutput exited with non zero status code."
<< " clang_path=" << clang_path << " status=" << status
<< " argv=" << argv
<< " compiler_info_envs=" << compiler_info_envs << " cwd=" << cwd
<< " output=" << output;
return "";
}
return output;
}
} // anonymous namespace
void VCCompilerInfoBuilder::SetTypeSpecificCompilerInfo(
const CompilerFlags& flags,
const string& local_compiler_path,
const string& abs_local_compiler_path,
const std::vector<string>& compiler_info_envs,
CompilerInfoData* data) const {
const VCFlags& vc_flags = static_cast<const VCFlags&>(flags);
if (VCFlags::IsClangClCommand(local_compiler_path)) {
SetClangClSpecificCompilerInfo(vc_flags, local_compiler_path,
abs_local_compiler_path, compiler_info_envs,
data);
} else {
SetClexeSpecificCompilerInfo(vc_flags, local_compiler_path,
abs_local_compiler_path, compiler_info_envs,
data);
}
}
void VCCompilerInfoBuilder::SetClexeSpecificCompilerInfo(
const VCFlags& vc_flags,
const string& local_compiler_path,
const string& abs_local_compiler_path,
const std::vector<string>& compiler_info_envs,
CompilerInfoData* data) const {
string vcflags_path = GetMyDirectory();
vcflags_path += "\\vcflags.exe";
data->mutable_cxx()->set_predefined_macros(data->cxx().predefined_macros() +
vc_flags.implicit_macros());
if (!VCCompilerInfoBuilder::GetVCVersion(
abs_local_compiler_path, compiler_info_envs, vc_flags.cwd(),
data->mutable_version(), data->mutable_target())) {
AddErrorMessage(
"Failed to get cl.exe version for " + abs_local_compiler_path, data);
LOG(ERROR) << data->error_message();
return;
}
if (!GetVCDefaultValues(abs_local_compiler_path, vcflags_path,
vc_flags.compiler_info_flags(), compiler_info_envs,
vc_flags.cwd(), data->lang(), data)) {
AddErrorMessage(
"Failed to get cl.exe system include path "
" or predifined macros for " +
abs_local_compiler_path,
data);
LOG(ERROR) << data->error_message();
return;
}
// TODO: collect executable resources?
}
void VCCompilerInfoBuilder::SetClangClSpecificCompilerInfo(
const VCFlags& vc_flags,
const string& local_compiler_path,
const string& abs_local_compiler_path,
const std::vector<string>& compiler_info_envs,
CompilerInfoData* data) const {
const string& lang_flag = vc_flags.is_cplusplus() ? "/TP" : "/TC";
if (!ClangCompilerInfoBuilderHelper::SetBasicCompilerInfo(
local_compiler_path, vc_flags.compiler_info_flags(),
compiler_info_envs, vc_flags.cwd(), lang_flag,
vc_flags.resource_dir(), vc_flags.is_cplusplus(), false, data)) {
DCHECK(data->has_error_message());
// If error occurred in SetBasicCompilerInfo, we do not need to
// continue.
return;
}
const string& sharp_output =
GetClangClSharpOutput(local_compiler_path, vc_flags.compiler_info_flags(),
compiler_info_envs, vc_flags.cwd());
if (sharp_output.empty() ||
!ClangCompilerInfoBuilderHelper::ParseClangVersionTarget(
sharp_output, data->mutable_version(), data->mutable_target())) {
AddErrorMessage(
"Failed to get version string for " + abs_local_compiler_path, data);
LOG(ERROR) << data->error_message();
return;
}
// --- Experimental. Add compiler resource.
{
std::vector<string> resource_paths_to_collect;
// local compiler.
resource_paths_to_collect.push_back(local_compiler_path);
// TODO: Not sure the whole list of dlls to run clang-cl.exe
// correctly. However, `dumpbin /DEPENDENTS clang-cl.exe` prints nothing
// special, so currently I don't collect dlls. Some dlls might be necessary
// to use some feature.
for (const auto& resource_path : resource_paths_to_collect) {
CompilerInfoData::ResourceInfo r;
if (!CompilerInfoBuilder::ResourceInfoFromPath(
vc_flags.cwd(), resource_path,
CompilerInfoData::EXECUTABLE_BINARY, &r)) {
AddErrorMessage("failed to get VC resource info for " + resource_path,
data);
return;
}
*data->add_resource() = std::move(r);
}
}
}
/* static */
bool VCCompilerInfoBuilder::ParseVCVersion(const string& vc_logo,
string* version,
string* target) {
// VC's logo format:
// ... Version 16.00.40219.01 for 80x86
// so we return cl 16.00.40219.01
string::size_type pos = vc_logo.find("Version ");
string::size_type pos2 = vc_logo.find(" for");
string::size_type pos3 = vc_logo.find("\r");
if (pos == string::npos || pos2 == string::npos || pos3 == string::npos ||
pos2 < pos || pos3 < pos2) {
LOG(INFO) << "Unable to parse cl.exe output."
<< " vc_logo=" << vc_logo;
return false;
}
pos += 8; // 8: length of "Version "
*version = vc_logo.substr(pos, pos2 - pos);
*target = vc_logo.substr(pos2 + 5, pos3 - pos2 - 5);
return true;
}
/* static */
bool VCCompilerInfoBuilder::GetVCVersion(const string& cl_exe_path,
const std::vector<string>& env,
const string& cwd,
string* version,
string* target) {
std::vector<string> argv;
argv.push_back(cl_exe_path);
int32_t status = 0;
string vc_logo;
{
GOMA_COUNTERZ("ReadCommandOutput(vc version)");
vc_logo = ReadCommandOutput(cl_exe_path, argv, env, cwd,
MERGE_STDOUT_STDERR, &status);
}
if (status != 0) {
LOG(ERROR) << "ReadCommandOutput exited with non zero status code."
<< " cl_exe_path=" << cl_exe_path
<< " status=" << status
<< " argv=" << argv
<< " env=" << env
<< " cwd=" << cwd
<< " vc_logo=" << vc_logo;
return false;
}
if (!ParseVCVersion(vc_logo, version, target)) {
LOG(ERROR) << "Failed to parse VCVersion."
<< " cl_exe_path=" << cl_exe_path
<< " status=" << status
<< " argv=" << argv
<< " env=" << env
<< " cwd=" << cwd
<< " vc_logo=" << vc_logo;
return false;
}
return true;
}
/* static */
bool VCCompilerInfoBuilder::ParseVCOutputString(
const string& output,
std::vector<string>* include_paths,
string* predefined_macros) {
std::vector<string> args;
// |output| doesn't contains command name, so adds "cl.exe" here.
args.push_back("cl.exe");
if (!ParseWinCommandLineToArgv(output, &args)) {
LOG(ERROR) << "Fail parse cmdline:" << output;
return false;
}
VCFlags flags(args, ".");
if (!flags.is_successful()) {
LOG(ERROR) << "ParseVCOutput error:" << flags.fail_message();
return false;
}
copy(flags.include_dirs().begin(), flags.include_dirs().end(),
back_inserter(*include_paths));
if (predefined_macros == nullptr)
return true;
std::ostringstream ss;
for (const auto& elm : flags.commandline_macros()) {
const string& macro = elm.first;
DCHECK(elm.second) << macro;
size_t found = macro.find('=');
if (found == string::npos) {
ss << "#define " << macro << "\n";
} else {
ss << "#define " << macro.substr(0, found) << " "
<< macro.substr(found + 1) << "\n";
}
}
*predefined_macros += ss.str();
return true;
}
// static
bool VCCompilerInfoBuilder::GetVCDefaultValues(
const string& cl_exe_path,
const string& vcflags_path,
const std::vector<string>& compiler_info_flags,
const std::vector<string>& compiler_info_envs,
const string& cwd,
const string& lang,
CompilerInfoData* compiler_info) {
// VC++ accepts two different undocumented flags to dump all predefined values
// in preprocessor. /B1 is for C and /Bx is for C++.
string vc_cpp_flags = "/Bx";
string vc_c_flags = "/B1";
vc_cpp_flags += vcflags_path;
vc_c_flags += vcflags_path;
// It does not matter that non-exist-file.cpp/.c is on disk or not. VCFlags
// will error out cl.exe and display the information we want before actually
// opening that file.
string output_cpp =
GetVCOutputString(cl_exe_path, vc_cpp_flags, "non-exist-file.cpp",
compiler_info_flags, compiler_info_envs, cwd);
string output_c =
GetVCOutputString(cl_exe_path, vc_c_flags, "non-exist-file.c",
compiler_info_flags, compiler_info_envs, cwd);
std::vector<string> cxx_system_include_paths;
if (!VCCompilerInfoBuilder::ParseVCOutputString(
output_cpp, &cxx_system_include_paths,
lang == "c++"
? compiler_info->mutable_cxx()->mutable_predefined_macros()
: nullptr)) {
return false;
}
for (const auto& p : cxx_system_include_paths) {
compiler_info->mutable_cxx()->add_cxx_system_include_paths(p);
}
std::vector<string> system_include_paths;
if (!VCCompilerInfoBuilder::ParseVCOutputString(
output_c, &system_include_paths,
lang == "c"
? compiler_info->mutable_cxx()->mutable_predefined_macros()
: nullptr)) {
return false;
}
for (const auto& p : system_include_paths) {
compiler_info->mutable_cxx()->add_system_include_paths(p);
}
return true;
}
} // namespace devtools_goma