blob: 38f13c00237f2906baf81cb2990394d1353052fe [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 "cpp_include_processor.h"
#include <time.h>
#ifndef _WIN32
#include <unistd.h>
#endif
#include <iostream>
#include "absl/strings/str_split.h"
#include "compiler_flags.h"
#include "file_helper.h"
#include "glog/logging.h"
#include "glog/stl_logging.h"
#include "goma_init.h"
#include "include_cache.h"
#include "ioutil.h"
#include "list_dir_cache.h"
#include "path.h"
#include "path_resolver.h"
#include "scoped_tmp_file.h"
#include "subprocess.h"
#if HAVE_CPU_PROFILER
#include <gperftools/profiler.h>
#endif
// TODO: share this code with include_processor_unittest.
std::set<string> GetExpectedFiles(const std::vector<string>& args,
const std::vector<string>& env,
const string& cwd) {
std::set<string> expected_files;
#ifndef _WIN32
// TODO: ReadCommandOutputByPopen couldn't read large outputs
// and causes exit=512, so use tmpfile.
devtools_goma::ScopedTmpFile tmpfile("include_processor_verify");
tmpfile.Close();
std::vector<string> run_args;
for (size_t i = 0; i < args.size(); ++i) {
const string& arg = args[i];
if (strncmp(arg.c_str(), "-M", 2) == 0) {
if (arg == "-MF" || arg == "-MT" || arg == "-MQ") {
++i;
}
continue;
}
if (arg == "-o") {
++i;
continue;
}
if (strncmp(arg.c_str(), "-o", 2) == 0) {
continue;
}
run_args.push_back(arg);
}
run_args.push_back("-M");
run_args.push_back("-MF");
run_args.push_back(tmpfile.filename());
std::vector<string> run_env(env);
run_env.push_back("LC_ALL=C");
// The output format of -M will be
//
// stdio: /usr/include/stdio.h /usr/include/features.h \\\n
// /usr/include/sys/cdefs.h /usr/include/bits/wordsize.h \\\n
// ...
int status;
devtools_goma::ReadCommandOutputByPopen(run_args[0], run_args, run_env, cwd,
devtools_goma::MERGE_STDOUT_STDERR,
&status);
if (status != 0) {
LOG(INFO) << "args:" << run_args;
LOG(INFO) << "env:" << run_env;
LOG(FATAL) << "status:" << status;
}
string output;
CHECK(devtools_goma::ReadFileToString(tmpfile.filename(), &output));
std::vector<string> files = ToVector(
absl::StrSplit(output, absl::ByAnyChar(" \n\r\\"), absl::SkipEmpty()));
devtools_goma::PathResolver pr;
// Skip the first element as it's the make target.
for (size_t i = 1; i < files.size(); i++) {
const string& file = files[i];
// Need normalization as GCC may output a same file in different way.
// TODO: don't use ResolvePath.
expected_files.insert(
pr.ResolvePath(file::JoinPathRespectAbsolute(cwd, file)));
}
#endif
return expected_files;
}
std::set<string> NormalizePaths(const string& cwd,
const std::set<string>& paths) {
std::set<string> normalized;
for (const auto& iter : paths) {
normalized.insert(devtools_goma::PathResolver::ResolvePath(
file::JoinPathRespectAbsolute(cwd, iter)));
}
return normalized;
}
int CompareFiles(const std::set<string>& expected_files,
const std::set<string>& actual_files) {
std::vector<string> matched;
std::vector<string> extra;
std::vector<string> missing;
std::set_intersection(expected_files.begin(), expected_files.end(),
actual_files.begin(), actual_files.end(),
back_inserter(matched));
std::set_difference(actual_files.begin(), actual_files.end(),
expected_files.begin(), expected_files.end(),
back_inserter(extra));
std::set_difference(expected_files.begin(), expected_files.end(),
actual_files.begin(), actual_files.end(),
back_inserter(missing));
for (const auto& extra_iter : extra) {
LOG(INFO) << "Extra include:" << extra_iter;
}
for (const auto& missing_iter : missing) {
LOG(ERROR) << "Missing include:" << missing_iter;
}
LOG(INFO) << "matched:" << matched.size() << " extra:" << extra.size()
<< " missing:" << missing.size();
return missing.size();
}
void GetAdditionalEnv(const char** envp,
const char* name,
std::vector<string>* envs) {
int namelen = strlen(name);
for (const char** e = envp; *e; e++) {
if (
#ifdef _WIN32
_strnicmp(*e, name, namelen) == 0
#else
strncmp(*e, name, namelen) == 0
#endif
&& (*e)[namelen] == '=') {
envs->push_back(*e);
return;
}
}
}
int main(int argc, char* argv[], const char** envp) {
devtools_goma::Init(argc, argv, envp);
devtools_goma::InitLogging(argv[0]);
devtools_goma::ListDirCache::Init(1024);
devtools_goma::IncludeCache::Init(32, false);
bool verify_mode = false;
if (argc >= 2 && !strcmp(argv[1], "--verify")) {
verify_mode = true;
argc--;
argv++;
#ifdef _WIN32
std::cerr << "--verify is not yet supported on win32" << std::endl;
exit(1);
#endif
}
int loop_count = 1;
if (argc >= 2 && absl::StartsWith(argv[1], "--count=")) {
loop_count = atoi(argv[1] + 8);
argc--;
argv++;
std::cerr << "Run CppIncludeProcessor::GetIncludeFiles " << loop_count
<< " times." << std::endl;
}
#ifndef _WIN32
if (argc == 1) {
std::cerr << argv[0] << " [full path of local compiler [args]]"
<< std::endl;
std::cerr << "e.g.: " << argv[0] << " /usr/bin/gcc -c tmp.c" << std::endl;
exit(1);
}
if (argv[1][0] != '/') {
std::cerr << "argv[1] is not absolute path for local compiler."
<< std::endl;
exit(1);
}
devtools_goma::InstallReadCommandOutputFunc(
devtools_goma::ReadCommandOutputByPopen);
#else
if (argc == 1) {
std::cerr << argv[0] << " [full path of local compiler [args]]"
<< std::endl;
std::cerr << "e.g.: " << argv[0] << " C:\\vs\\vc\\bin\\cl.exe /c c1.c"
<< std::endl;
std::cerr << "Compiler path must be absolute path." << std::endl;
exit(1);
}
devtools_goma::InstallReadCommandOutputFunc(
devtools_goma::ReadCommandOutputByRedirector);
#endif
devtools_goma::IncludeFileFinder::Init(false);
const string cwd = devtools_goma::GetCurrentDirNameOrDie();
std::vector<string> args;
for (int i = 1; i < argc; i++)
args.push_back(argv[i]);
std::unique_ptr<devtools_goma::CompilerFlags> flags(
devtools_goma::CompilerFlags::MustNew(args, cwd));
std::vector<string> compiler_info_envs;
flags->GetClientImportantEnvs(envp, &compiler_info_envs);
// These env variables are needed to run cl.exe
GetAdditionalEnv(envp, "PATH", &compiler_info_envs);
GetAdditionalEnv(envp, "TMP", &compiler_info_envs);
GetAdditionalEnv(envp, "TEMP", &compiler_info_envs);
devtools_goma::CompilerInfoBuilder cib;
std::unique_ptr<devtools_goma::CompilerInfoData> cid(
cib.FillFromCompilerOutputs(*flags, args[0], compiler_info_envs));
devtools_goma::CompilerInfo compiler_info(std::move(cid));
if (compiler_info.HasError()) {
std::cerr << compiler_info.error_message() << std::endl;
exit(1);
}
std::set<string> include_files;
#if HAVE_CPU_PROFILER
ProfilerStart(file::JoinPathRespectAbsolute(
FLAGS_TMP_DIR, FLAGS_INCLUDE_PROCESSOR_CPU_PROFILE_FILE)
.c_str());
#endif
for (int i = 0; i < loop_count; ++i) {
devtools_goma::CppIncludeProcessor include_processor;
devtools_goma::FileStatCache file_stat_cache;
include_files.clear();
clock_t start_time = clock();
for (const auto& iter : flags->input_filenames()) {
bool ok = include_processor.GetIncludeFiles(
iter, cwd, *flags, compiler_info, &include_files, &file_stat_cache);
if (!ok) {
std::cerr << "GetIncludeFiles failed" << std::endl;
exit(1);
}
}
clock_t end_time = clock();
// Show the result only for the first time.
if (i == 0) {
for (const auto& iter : include_files) {
std::cout << iter << std::endl;
}
std::cerr << "listed/skipped/total files: " << include_files.size()
<< " / " << include_processor.cpp_parser()->skipped_files()
<< " / " << include_processor.cpp_parser()->total_files()
<< std::endl;
}
if (loop_count != 1) {
std::cerr << "Run " << i << ": ";
}
std::cerr << (end_time - start_time) * 1000.0 / CLOCKS_PER_SEC << "msec"
<< std::endl;
}
#if HAVE_CPU_PROFILER
ProfilerStop();
#endif
if (verify_mode) {
for (const auto& iter : flags->input_filenames()) {
include_files.insert(file::JoinPathRespectAbsolute(cwd, iter));
}
std::set<string> actual = NormalizePaths(cwd, include_files);
std::set<string> expected = GetExpectedFiles(args, compiler_info_envs, cwd);
std::cout << "expected" << std::endl;
for (const auto& iter : expected) {
std::cout << iter << std::endl;
}
std::cout << "compare" << std::endl;
int missings = CompareFiles(expected, actual);
if (missings > 0) {
LOG(ERROR) << "missing files:" << missings;
exit(1);
}
}
devtools_goma::IncludeCache::Quit();
devtools_goma::ListDirCache::Quit();
}