blob: 69a82ce45861a2508e9217c9006aa48886048c12 [file] [log] [blame]
// Copyright 2010 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.
#ifndef _WIN32
#include <dirent.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#else
#include "config_win.h"
#endif
#ifdef __FreeBSD__
#include <sys/param.h>
#endif
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/match.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "autolock_timer.h"
#include "clang_modules/modulemap/cache.h"
#include "clang_tidy_flags.h"
#include "compiler_flags.h"
#include "compiler_info.h"
#include "content.h"
#include "counterz.h"
#include "cpp_directive.h"
#include "cpp_directive_parser.h"
#include "cpp_include_processor.h"
#include "cpp_parser.h"
#include "directive_filter.h"
#include "env_flags.h"
#include "file_dir.h"
#include "filesystem.h"
#include "flag_parser.h"
#include "gcc_flags.h"
#include "glog/logging.h"
#include "glog/stl_logging.h"
#include "glog/vlog_is_on.h"
#include "include_cache.h"
#include "include_file_utils.h"
#include "ioutil.h"
#include "list_dir_cache.h"
#include "lockhelper.h"
#include "path.h"
#include "path_resolver.h"
#include "scoped_fd.h"
#include "util.h"
#include "vc_flags.h"
#ifdef _WIN32
#include "path_resolver.h"
#include "posix_helper_win.h"
#endif
using std::string;
namespace devtools_goma {
namespace {
// Reads content from |filepath| and set |next_current_directory|.
// If |file_stat_cache| has a FileStat for |filepath|, we use it.
// Otherwise we take FileStat for |filepath| and stores it to |file_stat_cache|.
// We don't take |file_stat_cache| ownership.
IncludeItem TryInclude(const string& cwd,
const string& filepath,
string* next_current_directory,
FileStatCache* file_stat_cache) {
GOMA_COUNTERZ("TryInclude");
const string abs_filepath = file::JoinPathRespectAbsolute(cwd, filepath);
FileStat file_stat(file_stat_cache->Get(abs_filepath));
if (!file_stat.IsValid()) {
return IncludeItem();
}
if (file_stat.is_directory) {
VLOG(2) << "TryInclude but dir:" << abs_filepath;
return IncludeItem();
}
CHECK(IncludeCache::IsEnabled())
<< "IncludeCache is not enabled. "
<< "You forget to call IncludeCache::Init()?";
*next_current_directory = string(file::Dirname(filepath));
return IncludeCache::instance()->GetIncludeItem(abs_filepath, file_stat);
}
} // anonymous namespace
class IncludePathsObserver : public CppParser::IncludeObserver {
public:
IncludePathsObserver(std::string cwd,
CppParser* parser,
std::set<string>* shared_include_files,
FileStatCache* file_stat_cache,
IncludeFileFinder* include_file_finder)
: cwd_(std::move(cwd)),
parser_(parser),
shared_include_files_(shared_include_files),
file_stat_cache_(file_stat_cache),
include_file_finder_(include_file_finder) {
CHECK(parser_);
CHECK(shared_include_files_);
CHECK(file_stat_cache_);
}
bool HandleInclude(const string& path,
const string& current_directory,
const string& current_filepath,
char quote_char, // '"' or '<'
int include_dir_index) override {
// shared_include_files_ contains a set of include files for compilers.
// It's output variables of IncludePathsObserver.
// parser_->IsProcessedFile(filepath) indicates filepath was already parsed
// and no need to parse it again.
// So, if it returns true, shared_include_files_ must have filepath.
// In other words, there is a case shared_include_files_ have the filepath,
// but parser_->IsProcessedFile(filepath) returns false. It means
// the filepath once parsed, but it needs to parse it again (for example
// macro changed).
// parser_->AddFileInput should be called to let parser_ parse the file.
// include_dir_index is an index to start searching from.
//
// for #include "...", include_dir_index is current dir index of
// the file that is including the path. note that include_dir_index
// would not be kCurrentDirIncludeDirIndex, since CppParser needs
// to keep dir index for include file. i.e. an included file will have
// the same include dir index as file that includes the file.
//
// for #include <...>, it is bracket_include_dir_index.
//
// for #include_next, it will be next include dir index of file
// that is including the path. (always quote_char=='<').
CHECK(!path.empty()) << current_filepath;
VLOG(2) << current_filepath << ": including " << quote_char << path
<< " dir:" << current_directory
<< " include_dir_index:" << include_dir_index;
string next_current_directory;
string filepath;
if (quote_char == '"') {
// Look for current directory.
if (HandleIncludeInDir(current_directory, path, include_dir_index,
&next_current_directory)) {
return true;
}
VLOG(2) << "not found in curdir:" << current_directory;
// If not found in current directory, try all include paths.
include_dir_index = CppParser::kIncludeDirIndexStarting;
}
// Look for include dirs from |include_dir_index|.
int dir_index = include_dir_index;
if (!include_file_finder_->Lookup(path, &filepath, &dir_index) &&
!include_file_finder_->LookupSubframework(path, current_directory,
&filepath)) {
VLOG(2) << "Not found: " << path;
return false;
}
VLOG(3) << "Lookup => " << filepath << " dir_index=" << dir_index;
if (parser_->IsProcessedFile(filepath, include_dir_index)) {
VLOG(2) << "Already processed:" << quote_char << filepath;
return true;
}
IncludeItem include_item =
TryInclude(cwd_, filepath, &next_current_directory, file_stat_cache_);
if (include_item.IsValid()) {
if (IncludeFileFinder::gch_hack_enabled() &&
absl::EndsWith(filepath, GOMA_GCH_SUFFIX) &&
!absl::EndsWith(path, GOMA_GCH_SUFFIX)) {
VLOG(2) << "Found a precompiled header: " << filepath;
shared_include_files_->insert(filepath);
return true;
}
VLOG(2) << "Looking into " << filepath << " index=" << dir_index;
shared_include_files_->insert(filepath);
parser_->AddFileInput(std::move(include_item), filepath,
next_current_directory, dir_index);
return true;
}
VLOG(2) << "include file not found in dir_cache?";
return false;
}
bool HasInclude(const string& path,
const string& current_directory,
const string& current_filepath,
char quote_char, // '"' or '<'
int include_dir_index) override {
CHECK(!path.empty()) << current_filepath;
string next_current_directory;
string filepath;
if (quote_char == '"') {
if (HasIncludeInDir(current_directory, path, current_filepath)) {
return true;
}
include_dir_index = CppParser::kIncludeDirIndexStarting;
}
int dir_index = include_dir_index;
if (!include_file_finder_->Lookup(path, &filepath, &dir_index)) {
VLOG(2) << "Not found: " << path;
return false;
}
std::string abs_filepath =
file::JoinPathRespectAbsolute(cwd_, filepath);
if (shared_include_files_->count(filepath) ||
access(abs_filepath.c_str(), R_OK) == 0) {
DCHECK(!file::IsDirectory(abs_filepath, file::Defaults()).ok())
<< abs_filepath;
shared_include_files_->insert(std::move(filepath));
return true;
}
return false;
}
private:
bool CanPruneWithTopPathComponent(const string& dir, const string& path) {
// we don't need to care about case ignoreness here.
// we'll access filesystem, so filesystem would handle case senstiveness.
const std::string& dir_with_top_path_component = file::JoinPath(
dir, IncludeFileFinder::TopPathComponent(path, false));
return !file_stat_cache_->Get(dir_with_top_path_component).IsValid();
}
bool HandleIncludeInDir(const string& dir,
const string& path,
int include_dir_index,
string* next_current_directory) {
GOMA_COUNTERZ("handle include try");
if (CanPruneWithTopPathComponent(file::JoinPathRespectAbsolute(cwd_, dir),
path)) {
VLOG(2) << "can prune with top path component:"
<< " cwd=" << cwd_
<< " dir=" << dir
<< " path=" << path;
GOMA_COUNTERZ("handle include pruned");
return false;
}
string filepath =
PathResolver::PlatformConvert(file::JoinPathRespectAbsolute(dir, path));
VLOG(2) << "handle include in dir: " << filepath;
if (IncludeFileFinder::gch_hack_enabled()) {
const string& gchpath = filepath + GOMA_GCH_SUFFIX;
IncludeItem include_item(
TryInclude(cwd_, gchpath, next_current_directory, file_stat_cache_));
if (include_item.IsValid()) {
VLOG(2) << "Found a pre-compiled header: " << gchpath;
shared_include_files_->insert(gchpath);
// We should not check the content of pre-compiled headers.
return true;
}
}
if (parser_->IsProcessedFile(filepath, include_dir_index)) {
VLOG(2) << "Already processed: \"" << filepath << "\"";
return true;
}
IncludeItem include_item =
TryInclude(cwd_, filepath, next_current_directory, file_stat_cache_);
if (include_item.IsValid()) {
shared_include_files_->insert(filepath);
parser_->AddFileInput(std::move(include_item), filepath,
*next_current_directory, include_dir_index);
return true;
}
VLOG(2) << "include file not found in current directoy? filepath="
<< filepath;
return false;
}
bool HasIncludeInDir(const string& dir,
const string& path,
const string& current_filepath) {
std::string filepath = file::JoinPathRespectAbsolute(dir, path);
string abs_filepath = file::JoinPathRespectAbsolute(cwd_, filepath);
string abs_current_filepath =
file::JoinPathRespectAbsolute(cwd_, current_filepath);
abs_filepath = PathResolver::ResolvePath(abs_filepath);
bool is_current = (abs_filepath == abs_current_filepath);
if (is_current) {
shared_include_files_->insert(std::move(filepath));
return true;
}
if (!file::IsDirectory(abs_filepath, file::Defaults()).ok()) {
if (shared_include_files_->count(filepath)) {
return true;
}
if (access(abs_filepath.c_str(), R_OK) == 0) {
shared_include_files_->insert(std::move(filepath));
return true;
}
if (IncludeFileFinder::gch_hack_enabled() &&
access((abs_filepath + GOMA_GCH_SUFFIX).c_str(), R_OK) == 0) {
shared_include_files_->insert(filepath + GOMA_GCH_SUFFIX);
return true;
}
}
return false;
}
const std::string cwd_;
CppParser* parser_;
std::set<string>* shared_include_files_;
FileStatCache* file_stat_cache_;
IncludeFileFinder* include_file_finder_;
DISALLOW_COPY_AND_ASSIGN(IncludePathsObserver);
};
class IncludeErrorObserver : public CppParser::ErrorObserver {
public:
IncludeErrorObserver() {}
void HandleError(const string& error) override {
// Note that we don't set this error observer if VLOG_IS_ON(1) is false.
// If you need to change this code, make sure you'll modify
// set_error_observer call in CppIncludeProcessor::GetIncludeFiles()
// to be consistent with here.
VLOG(1) << error;
}
private:
DISALLOW_COPY_AND_ASSIGN(IncludeErrorObserver);
};
static void CopyIncludeDirs(const std::vector<string>& input_dirs,
const string& toolchain_root,
std::vector<string>* output_dirs) {
for (const auto& input_dir : input_dirs) {
const string& dir = file::JoinPath(
toolchain_root, PathResolver::PlatformConvert(input_dir));
output_dirs->push_back(dir);
}
}
#ifndef _WIN32
static void CopyOriginalFileFromHashCriteria(const string& filepath) {
static Lock mu;
if (access(filepath.c_str(), R_OK) == 0) {
return;
}
// Only one thread can copy the GCH.
AUTOLOCK(lock, &mu);
if (access(filepath.c_str(), R_OK) == 0) {
return;
}
const string& hash_criteria_filepath = filepath + ".gch.hash-criteria";
std::ifstream ifs(hash_criteria_filepath.c_str());
if (!ifs) {
return;
}
string line;
getline(ifs, line);
const char* expected_prefix = "Contents of ";
if (!absl::StartsWith(line, expected_prefix)) {
return;
}
const string& original_filepath = line.substr(strlen(expected_prefix));
VLOG(1) << "hash criteria file found. original filepath: "
<< original_filepath;
const string tmp_filepath = filepath + ".tmp";
file::Copy(original_filepath, tmp_filepath, file::Overwrite());
rename(tmp_filepath.c_str(), filepath.c_str());
}
#endif
static bool NormalizePath(const string& path_to_normalize,
string* normalized_path) {
// TODO: Can't we remove this ifdef? Maybe we have make a code
// that is platform independent?
// Do we need to resolve symlink on Unix?
#ifndef _WIN32
std::unique_ptr<char[], decltype(&free)> path_buf(
realpath(path_to_normalize.c_str(), nullptr), free);
if (path_buf.get() == nullptr)
return false;
normalized_path->assign(path_buf.get());
#else
*normalized_path = PathResolver::ResolvePath(
PathResolver::PlatformConvert(path_to_normalize));
if (normalized_path->empty() ||
(GetFileAttributesA(normalized_path->c_str()) ==
INVALID_FILE_ATTRIBUTES)) {
return false;
}
#endif
return true;
}
static void MergeDirs(const std::string cwd,
const std::vector<string>& dirs,
std::vector<string>* include_dirs,
std::set<string>* seen_include_dir_set) {
for (const auto& dir : dirs) {
std::string abs_dir = file::JoinPathRespectAbsolute(cwd, dir);
string normalized_dir;
if (!NormalizePath(abs_dir, &normalized_dir)) {
continue;
}
// Remove duplicated dirs.
if (!seen_include_dir_set->insert(normalized_dir).second) {
continue;
}
include_dirs->push_back(dir);
}
}
static void MergeIncludeDirs(const std::string& cwd,
const std::vector<string>& nonsystem_include_dirs,
const std::vector<string>& system_include_dirs,
std::vector<string>* include_dirs) {
std::set<string> seen_include_dir_set;
// We check system include paths first because we should give more
// priority to system paths than non-system paths when we check
// duplicates of them. We will push back the system include paths
// into include_paths later because the order of include paths
// should be non-system path first.
std::vector<string> unique_system_include_dirs;
MergeDirs(cwd, system_include_dirs, &unique_system_include_dirs,
&seen_include_dir_set);
MergeDirs(cwd, nonsystem_include_dirs, include_dirs, &seen_include_dir_set);
copy(unique_system_include_dirs.begin(), unique_system_include_dirs.end(),
back_inserter(*include_dirs));
}
bool CppIncludeProcessor::GetIncludeFiles(const string& filename,
const string& current_directory,
const CompilerFlags& compiler_flags,
const CxxCompilerInfo& compiler_info,
std::set<string>* include_files,
FileStatCache* file_stat_cache) {
DCHECK(!current_directory.empty());
DCHECK(file::IsAbsolutePath(current_directory)) << current_directory;
std::vector<string> non_system_include_dirs;
std::vector<string> root_includes;
std::vector<string> user_framework_dirs;
std::vector<std::pair<string, bool>> commandline_macros;
#if _WIN32
bool ignore_case = true;
#else
bool ignore_case = false;
#endif
if (compiler_flags.type() == CompilerFlagType::Gcc) {
const GCCFlags& flags = static_cast<const GCCFlags&>(compiler_flags);
non_system_include_dirs = flags.non_system_include_dirs();
root_includes = flags.root_includes();
user_framework_dirs = flags.framework_dirs();
commandline_macros = flags.commandline_macros();
// -Xclang -emit-module is not supported yet. Do fallback.
// b/24956317
if (flags.has_emit_module()) {
LOG(INFO) << "-Xclang -emit-module is not supported yet; force fallback";
return false;
}
} else if (compiler_flags.type() == CompilerFlagType::Clexe) {
const VCFlags& flags = static_cast<const VCFlags&>(compiler_flags);
non_system_include_dirs = flags.include_dirs();
root_includes = flags.root_includes();
commandline_macros = flags.commandline_macros();
// in chromium, clang-cl on linux
// https://chromium.googlesource.com/chromium/src/+/lkcr/docs/win_cross.md
// it is expected to use ciopfs for win_sdk, but not for chromium source.
// (depot_tools configured it so)
ignore_case = true;
} else if (compiler_flags.type() == CompilerFlagType::ClangTidy) {
const ClangTidyFlags& flags =
static_cast<const ClangTidyFlags&>(compiler_flags);
non_system_include_dirs = flags.non_system_include_dirs();
root_includes = flags.root_includes();
user_framework_dirs = flags.framework_dirs();
commandline_macros = flags.commandline_macros();
} else {
LOG(FATAL) << "Bad compiler_flags for CppIncludeProcessor: "
<< compiler_flags.DebugString();
}
VLOG(3) << "non_system_include_dirs=" << non_system_include_dirs;
VLOG(3) << "root_includes=" << root_includes;
VLOG(3) << "user_framework_dirs=" << user_framework_dirs;
VLOG(3) << "commandline_macros=" << commandline_macros;
for (const auto& include_dir : non_system_include_dirs) {
// TODO: Ideally, we should not add .hmap file if this
// file doesn't exist.
if (absl::EndsWith(include_dir, ".hmap")) {
include_files->insert(include_dir);
}
}
std::vector<string> quote_dirs;
CopyIncludeDirs(compiler_info.quote_include_paths(), "", &quote_dirs);
std::vector<string> all_system_include_dirs;
if (compiler_info.lang().find("c++") != string::npos) {
CopyIncludeDirs(compiler_info.cxx_system_include_paths(),
compiler_info.toolchain_root(), &all_system_include_dirs);
} else {
CopyIncludeDirs(compiler_info.system_include_paths(),
compiler_info.toolchain_root(), &all_system_include_dirs);
}
// The first element of include_dirs.include_dirs represents the current input
// directory. It's not specified by -I, but we need to handle it when
// including file with #include "".
std::vector<std::string> include_dirs;
std::vector<std::string> framework_dirs;
include_dirs.push_back(current_directory);
copy(quote_dirs.begin(), quote_dirs.end(), back_inserter(include_dirs));
cpp_parser_.set_bracket_include_dir_index(include_dirs.size());
VLOG(2) << "bracket include dir index=" << include_dirs.size();
MergeIncludeDirs(current_directory, non_system_include_dirs,
all_system_include_dirs, &include_dirs);
#ifndef _WIN32
std::vector<string> abs_user_framework_dirs;
CopyIncludeDirs(user_framework_dirs, "", &abs_user_framework_dirs);
std::vector<string> system_framework_dirs;
CopyIncludeDirs(compiler_info.system_framework_paths(),
compiler_info.toolchain_root(), &system_framework_dirs);
MergeIncludeDirs(current_directory, abs_user_framework_dirs,
system_framework_dirs, &framework_dirs);
#else
CHECK(compiler_info.system_framework_paths().empty());
#endif
// TODO: cleanup paths (// -> /, /./ -> /) in include_dirs
// Note that we should not use ResolvePath for these dirs.
IncludeFileFinder include_file_finder(current_directory, ignore_case,
&include_dirs, &framework_dirs,
file_stat_cache);
std::vector<std::pair<string, int>> root_includes_with_index =
CalculateRootIncludesWithIncludeDirIndex(
root_includes, current_directory, compiler_flags,
&include_file_finder, include_files);
root_includes_with_index.emplace_back(PathResolver::PlatformConvert(filename),
CppParser::kCurrentDirIncludeDirIndex);
IncludePathsObserver include_observer(current_directory,
&cpp_parser_, include_files,
file_stat_cache, &include_file_finder);
IncludeErrorObserver error_observer;
cpp_parser_.set_include_observer(&include_observer);
if (VLOG_IS_ON(1))
cpp_parser_.set_error_observer(&error_observer);
cpp_parser_.SetCompilerInfo(&compiler_info);
if (compiler_flags.type() == CompilerFlagType::Clexe) {
cpp_parser_.set_is_vc();
}
// True if the compiler is gcc-like and we are building in hosted mode.
bool gcc_like_hosted = false;
if (compiler_flags.type() == CompilerFlagType::Gcc) {
const GCCFlags& flags = static_cast<const GCCFlags&>(compiler_flags);
gcc_like_hosted = !(flags.has_ffreestanding() || flags.has_fno_hosted());
}
if (gcc_like_hosted) {
// CompilerInfo was generated with -ffreestanding, and set
// __STDC_HOSTED__=0 - we must override this.
cpp_parser_.DeleteMacro("__STDC_HOSTED__");
cpp_parser_.AddMacroByString("__STDC_HOSTED__", "1");
}
for (const auto& commandline_macro : commandline_macros) {
const string& macro = commandline_macro.first;
if (commandline_macro.second) {
size_t found = macro.find('=');
if (found == string::npos) {
// https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html
// -D name
// Predefine name as a macro, with definition 1.
cpp_parser_.AddMacroByString(macro, "1");
continue;
}
const string& key = macro.substr(0, found);
const string& value = macro.substr(found + 1, macro.size() - (found + 1));
cpp_parser_.AddMacroByString(key, value);
} else {
cpp_parser_.DeleteMacro(macro);
}
}
if (gcc_like_hosted) {
// From GCC 4.8, stdc-predef.h is automatically included without
// -ffreestanding. Also, -fno-hosted is equivalent to -ffreestanding.
// See also: https://gcc.gnu.org/gcc-4.8/porting_to.html
if (!absl::StrContains(compiler_info.name(), "clang")) {
// Note: this requires that the compiler macros were extracted with
// -ffreestanding, otherwise stdc-predef.h's header guard will be
// defined and this will be a no-op.
// Some environment might not have stdc-predef.h (e.g. android).
// If cpp_parser tries including a missing file, it is considered as
// a hard error. So, we have to check it exists or not.
// gcc-4 does not have __has_include (it's from gcc-5, IIRC,
// see https://gcc.gnu.org/gcc-5/changes.html#c-family),
// but we have to try to include stdc-predef.h.
// So, here, we try to include stdc-predef.h anyway, and clear the error
// if an error has occured.
// See b/124756380.
const string stdc_predef_input(
"#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)\n"
"#include <stdc-predef.h>\n"
"#endif\n");
cpp_parser_.AddStringInput(stdc_predef_input, "(stdc-predef)");
bool prev_disabled = cpp_parser_.disabled();
if (!cpp_parser_.ProcessDirectives()) {
LOG(WARNING) << "failed to handle stdc-predef";
if (!prev_disabled) {
cpp_parser_.ClearDisabled();
}
}
// Since base_file_ will be updated in the last AddStringInput, we need
// to clear it. Otherwise, test will fail.
cpp_parser_.ClearBaseFile();
}
}
for (const auto& input_index : root_includes_with_index) {
const string& input = input_index.first;
const int dir_index = input_index.second;
const std::string& abs_input =
file::JoinPathRespectAbsolute(current_directory, input);
std::unique_ptr<Content> content(Content::CreateFromFile(abs_input));
if (!content) {
LOG(ERROR) << "root include:" << abs_input << " not found";
return false;
}
// TODO: To mitigate b/78094849, let me run directive filter for
// sources, too.
SharedCppDirectives directives(CppDirectiveParser::ParseFromContent(
*DirectiveFilter::MakeFilteredContent(*content), abs_input));
if (!directives) {
LOG(ERROR) << "failed to parse directives: " << abs_input;
return false;
}
VLOG(2) << "Looking into " << abs_input;
string input_basedir = string(file::Dirname(input));
cpp_parser_.AddFileInput(IncludeItem(std::move(directives), ""), input,
input_basedir, dir_index);
if (!cpp_parser_.ProcessDirectives()) {
LOG(ERROR) << "cpp parser fatal error in " << abs_input;
return false;
}
}
if (compiler_flags.type() == CompilerFlagType::Gcc) {
const GCCFlags& flags = static_cast<const GCCFlags&>(compiler_flags);
if (flags.has_fmodules()) {
if (!AddClangModulesFiles(flags, current_directory, include_files,
file_stat_cache)) {
return false;
}
}
}
return true;
}
std::vector<std::pair<string, int>>
CppIncludeProcessor::CalculateRootIncludesWithIncludeDirIndex(
const std::vector<string>& root_includes,
const string& current_directory,
const CompilerFlags& compiler_flags,
IncludeFileFinder* include_file_finder,
std::set<string>* include_files) {
std::vector<std::pair<string, int>> result;
for (const auto& root_include : root_includes) {
string abs_filepath = PathResolver::PlatformConvert(
file::JoinPathRespectAbsolute(current_directory, root_include));
// TODO: this does not seem to apply to Windows. Need verify.
#ifndef _WIN32
if (IncludeFileFinder::gch_hack_enabled()) {
// If there is the precompiled header for this header, we'll send
// the precompiled header. Note that we don't need to check its content.
string gch_filepath = abs_filepath + GOMA_GCH_SUFFIX;
ScopedFd fd(ScopedFd::OpenForRead(gch_filepath));
if (fd.valid()) {
fd.Close();
VLOG(1) << "precompiled header found: " << gch_filepath;
include_files->insert(root_include + GOMA_GCH_SUFFIX);
continue;
}
}
#endif
if (access(abs_filepath.c_str(), R_OK) == 0) {
#ifndef _WIN32
// we don't support *.gch on Win32.
CopyOriginalFileFromHashCriteria(abs_filepath);
#endif
// -include can be used twice. So we need to keep it in result
// if it's duplicated.
include_files->insert(root_include);
result.emplace_back(root_include, CppParser::kCurrentDirIncludeDirIndex);
continue;
}
std::string filepath;
int dir_index = CppParser::kIncludeDirIndexStarting;
if (!include_file_finder->Lookup(root_include, &filepath, &dir_index)) {
LOG(INFO) << (compiler_flags.type() == CompilerFlagType::Clexe
? "/FI"
: "-include")
<< " not found: " << root_include;
result.emplace_back(root_include, CppParser::kCurrentDirIncludeDirIndex);
continue;
}
if (IncludeFileFinder::gch_hack_enabled() &&
absl::EndsWith(filepath, GOMA_GCH_SUFFIX)) {
VLOG(1) << "precompiled header found: " << filepath;
include_files->insert(filepath);
continue;
}
// -include can be used twice. So we need to keep it in result
// if it's duplicated.
include_files->insert(filepath);
result.emplace_back(filepath, dir_index);
}
return result;
}
bool CppIncludeProcessor::AddClangModulesFiles(
const GCCFlags& flags,
const string& current_directory,
std::set<string>* include_files,
FileStatCache* file_stat_cache) const {
// TODO: experiment support of clang modules.
// In this implementation, we don't read the content of module file.
// We just add it as an input.
// We assume -fmodules does not affect a list of include files.
// This will be 99% fine, but we might see problems if a user
// totally depend on clang modules behavior.
DCHECK(flags.has_fmodules());
// module-file (not module-map-file).
if (!flags.clang_module_file().second.empty()) {
string abs_path = file::JoinPathRespectAbsolute(
current_directory, flags.clang_module_file().second);
FileStat fs = file_stat_cache->Get(abs_path);
if (!fs.IsValid()) {
LOG(WARNING) << "module file not found: " << abs_path;
return false;
}
if (fs.is_directory) {
LOG(WARNING) << "directory is specified to module file: " << abs_path;
return false;
}
include_files->insert(flags.clang_module_file().second);
}
// module-map-file
if (!flags.clang_module_map_file().empty()) {
if (!modulemap::Cache::instance()->AddModuleMapFileAndDependents(
flags.clang_module_map_file(), current_directory, include_files,
file_stat_cache)) {
LOG(WARNING) << "failed to add a module map: "
<< flags.clang_module_map_file();
return false;
}
}
// module.modulemap is parsed only if flags.has_fimplicit_module_maps() is
// true.
if (flags.has_fimplicit_module_maps()) {
std::vector<string> dirs;
for (const auto& file : *include_files) {
dirs.emplace_back(file::Dirname(file));
}
std::sort(dirs.begin(), dirs.end());
dirs.erase(std::unique(dirs.begin(), dirs.end()), dirs.end());
for (const auto& d : dirs) {
// Check both module.modulemap and module.map. The latter is an older
// form.
for (const auto& name : {"module.modulemap", "module.map"}) {
string rel_path = file::JoinPath(d, name);
string abs_path =
file::JoinPathRespectAbsolute(current_directory, rel_path);
FileStat fs = file_stat_cache->Get(abs_path);
if (fs.IsValid() && !fs.is_directory) {
if (!modulemap::Cache::instance()->AddModuleMapFileAndDependents(
rel_path, current_directory, include_files,
file_stat_cache)) {
return false;
}
}
}
}
}
return true;
}
int CppIncludeProcessor::total_files() const {
return cpp_parser_.total_files();
}
int CppIncludeProcessor::skipped_files() const {
return cpp_parser_.skipped_files();
}
} // namespace devtools_goma