blob: 12961c09a45e922a6ec96934a147ead29c8a5f01 [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 DEVTOOLS_GOMA_CLIENT_COMPILER_INFO_H_
#define DEVTOOLS_GOMA_CLIENT_COMPILER_INFO_H_
#include <sys/types.h>
#include <time.h>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#include "absl/strings/string_view.h"
#include "compiler_specific.h"
#include "file_id.h"
#include "google/protobuf/repeated_field.h"
#include "gtest/gtest_prod.h"
#include "lockhelper.h"
#include "predefined_macros.h"
MSVC_PUSH_DISABLE_WARNING_FOR_PROTO()
#include "prototmp/compiler_info_data.pb.h"
MSVC_POP_WARNING()
using std::string;
namespace devtools_goma {
class CompilerFlags;
class GCCFlags;
// CompilerInfoBuilder provides methods to construct CompilerInfoData.
//
// CompielrInfoBuilder cib;
// std::unique_ptr<CompilerInfoData> data(
// cib.FillFromCompilerOutputs(....));
// CompilerInfo compiler_info(std::move(data));
class CompilerInfoBuilder {
public:
typedef std::pair<const char* const*, size_t> FeatureList;
CompilerInfoBuilder() {}
~CompilerInfoBuilder() {}
// Creates new CompilerInfoData* from compiler outputs.
// if found is true and error_message in it is empty,
// it successfully gets compiler info.
// if found is true and error_message in it is not empty,
// it finds local compiler but failed to get some information, such as
// system include paths.
// if found is false if it fails to find local compiler.
// Caller should take ownership of returned CompilerInfoData.
std::unique_ptr<CompilerInfoData> FillFromCompilerOutputs(
const CompilerFlags& flags,
const string& local_compiler_path,
const std::vector<string>& compiler_info_envs);
// helper methods.
// Parses output of "gcc -x <lang> -v -E /dev/null -o /dev/null", and
// extracts |qpaths| (for #include "..."),
// |paths| (for #include <...>) and |framework_paths|.
static bool SplitGccIncludeOutput(
const string& gcc_v_output,
std::vector<string>* qpaths,
std::vector<string>* paths,
std::vector<string>* framework_paths);
// Parses output of clang feature macros.
static bool ParseFeatures(const string& feature_output,
FeatureList object_macros,
FeatureList function_macros,
FeatureList feature,
FeatureList extension,
FeatureList attribute,
FeatureList cpp_attribute,
FeatureList declspec_attribute,
FeatureList builtins,
CompilerInfoData* compiler_info);
static bool GetPredefinedFeaturesAndExtensions(
const string& normal_compiler_path,
const string& lang_flag,
const std::vector<string>& compiler_info_flags,
const std::vector<string>& compiler_info_envs,
const string& cwd,
CompilerInfoData* compiler_info);
static bool GetAdditionalFlags(
const string& gxx_output, std::vector<string>* flags);
// Sets the compiler resource directory. asan_blacklist.txt etc. are
// located in this directory.
// Returns true if succeeded.
static bool GetResourceDir(const string& c_display_output,
CompilerInfoData* compiler_info);
// Returns false if GetExtraSubprograms failed to get subprogram info
// while a subprogram exists.
static bool GetExtraSubprograms(const string& normal_gcc_path,
const GCCFlags& flags,
const std::vector<string>& compiler_info_envs,
CompilerInfoData* compiler_info);
// Parses compile flags for subprograms, especially clang plugins.
static void ParseSubprogramFlags(const string& normal_gcc_path,
const GCCFlags& flags,
std::vector<string>* clang_plugins,
std::vector<string>* B_options,
bool* no_integrated_as);
// Parse |gcc_output| to get list of subprograms.
static void ParseGetSubprogramsOutput(const string& gcc_output,
std::vector<string>* paths);
// Returns true on success, and |subprograms| will have full path of
// external subprograms or empty vector if not found.
// Returns false on failure.
static bool 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>* subprograms);
// Returns true if |subprogram_paths| contain a path for as (assembler).
static bool HasAsPath(const std::vector<string>& subprogram_paths);
// Parses "-xc -v -E /dev/null" output and returns real clang path.
static string ParseRealClangPath(absl::string_view v_out);
// Get real compiler path.
// See: go/ma/resources-for-developers/goma-compiler-selection-mechanism
static string GetRealCompilerPath(const string& normal_gcc_path,
const string& cwd,
const std::vector<string>& envs);
// Get real subprogram path.
// See: go/ma/resources-for-developers/goma-compiler-selection-mechanism
static string GetRealSubprogramPath(const string& subprogram_path);
// Parses output of "javac", and extracts |version|.
static bool ParseJavacVersion(const string& vc_logo, string* version);
// Execute javac and get the string output for javac version
static bool GetJavacVersion(const string& javac,
const std::vector<string>& compiler_info_envs,
const string& cwd,
string* version);
// Parses output of "cl.exe", and extracts |version| and |target|.
static bool ParseVCVersion(
const string& vc_logo, string* version, string* target);
// Execute VC and get the string output for VC version
static bool GetVCVersion(
const string& cl_exe_path, const std::vector<string>& env,
const string& cwd,
string* version, string* target);
// Parses output of "cl.exe /nologo /Bxvcflags.exe non-exist-file.cpp" (C++)
// or "cl.exe /nologo /B1vcflags.exe non-exist-file.c" (C),
// and extracts |include_paths| and |predefined macros| in
// "#define FOO X\n" format.
// |predefined_macros| may be NULL (don't capture predefined macros
// in this case).
static bool ParseVCOutputString(
const string& output,
std::vector<string>* include_paths,
string* predefined_macros);
// Parses output of clang / clang-cl -### result to get
// |version| and |target|.
static bool ParseClangVersionTarget(const string& sharp_output,
string* version,
string* target);
// Executes clang-tidy and gets the string output for clang-tidy version.
static bool GetClangTidyVersionTarget(
const string& clang_tidy_path,
const std::vector<string>& compiler_info_envs,
const string& cwd,
string* version,
string* target);
static bool ParseClangTidyVersionTarget(const string& output,
string* version,
string* target);
static bool SetBasicCompilerInfo(
const string& local_compiler_path,
const std::vector<string>& compiler_info_flags,
const std::vector<string>& compiler_info_envs,
const string& cwd,
const string& lang_flag,
bool is_cplusplus,
bool is_clang,
bool is_clang_tidy,
bool has_nostdinc,
CompilerInfoData* compiler_info);
static bool GetSystemIncludePaths(
const string& normal_compiler_path,
const std::vector<string>& compiler_info_flags,
const std::vector<string>& compiler_info_envs,
const string& cxx_display_output,
const string& c_display_output,
bool is_cplusplus,
bool has_nostdinc,
CompilerInfoData* compiler_info);
static bool GetPredefinedMacros(
const string& normal_compiler_path,
const std::vector<string>& compiler_info_flags,
const std::vector<string>& compiler_info_envs,
const string& cwd,
const string& lang_flag,
CompilerInfoData* compiler_info);
static bool 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);
// Set up system include_paths to be sent to goma backend via ExecReq.
// To make the compile deterministic, we sometimes need to use relative
// path system include paths, and UpdateIncludePaths automatically
// converts the paths.
static void UpdateIncludePaths(
const std::vector<string>& paths,
google::protobuf::RepeatedPtrField<string>* include_paths);
// Adds error message to CompilerInfo. When |failed_at| is not 0,
// it's also updated.
static void AddErrorMessage(const std::string& message,
CompilerInfoData* compiler_info);
// Overrides the current error message.
// if |message| is not empty, |failed_at| must be non-zero positive.
static void OverrideError(const std::string& message, time_t faile_at,
CompilerInfoData* compiler_info);
static bool SubprogramInfoFromPath(
const string& path, CompilerInfoData::SubprogramInfo* s);
void SetHashRewriteRule(const std::map<std::string, std::string>& rule);
static bool RewriteHashUnlocked(
const std::map<std::string, std::string>& rule,
CompilerInfoData* data);
// Returns compiler name to be used in ExecReq's CompilerSpec.
// If it fails to identify the compiler name, it returns empty string.
static string GetCompilerName(const CompilerInfoData& data);
void Dump(std::ostringstream* ss);
private:
ReadWriteLock rwlock_;
std::map<std::string, std::string> hash_rewrite_rule_;
DISALLOW_COPY_AND_ASSIGN(CompilerInfoBuilder);
};
// Represent how a compiler is configured.
// Used as const object.
class CompilerInfo {
public:
struct SubprogramInfo {
SubprogramInfo() {}
static void FromData(const CompilerInfoData::SubprogramInfo& info_data,
SubprogramInfo* info);
static SubprogramInfo FromPath(const string& path);
bool IsValid() const {
return file_id.IsValid() && !hash.empty() && !name.empty();
}
bool operator==(const SubprogramInfo& rhs) const {
return name == rhs.name &&
hash == rhs.hash &&
file_id == rhs.file_id;
}
string DebugString() const;
string name;
string hash;
FileId file_id;
};
// Takes ownership of data.
explicit CompilerInfo(std::unique_ptr<CompilerInfoData> data)
: data_(std::move(data)) {
Init();
}
~CompilerInfo() {}
string DebugString() const;
// Returns true if |local_compiler_path| is up to date.
// i.e. FileId of |local_compiler_path| matches |local_compiler_id|.
bool IsUpToDate(const string& local_compiler_path) const;
// Updates FileId to the current FileId when hash is matched.
// Returns false if hash doesn't match.
bool UpdateFileIdIfHashMatch(
std::unordered_map<string, string>* sha256_cache);
// Returns true if CompilerInfo has some error.
bool HasError() const { return data_->has_error_message(); }
bool IsSystemInclude(const string& filepath) const;
bool IsCwdRelative(const string& cwd) const;
const FileId& local_compiler_id() const { return local_compiler_id_; }
const string& local_compiler_path() const {
return data_->local_compiler_path();
}
string abs_local_compiler_path() const;
const string& local_compiler_hash() const {
return data_->local_compiler_hash();
}
const FileId& real_compiler_id() const { return real_compiler_id_; }
const string& real_compiler_path() const {
return data_->real_compiler_path();
}
const string& real_compiler_hash() const {
return data_->hash();
}
// compiler hash to identify the compiler in backend.
const string& request_compiler_hash() const;
// include paths could be relative path from cwd.
// Also, system include paths could be relative path from toolchain root
// (Windows NaCl toolchain only).
// You should file::JoinPathRespectAbsolute with cwd before you use it in
// include processor.
// quote dir is valid only if it exists. note quote dir may be cwd relative
// so it depends on cwd if dir is valid or not.
const std::vector<string>& quote_include_paths() const {
return quote_include_paths_;
}
const std::vector<string>& cxx_system_include_paths() const {
return cxx_system_include_paths_;
}
const std::vector<string>& system_include_paths() const {
return system_include_paths_;
}
const std::vector<string>& system_framework_paths() const {
return system_framework_paths_;
}
const string& toolchain_root() const {
return data_->toolchain_root();
}
const string& predefined_macros() const {
return data_->predefined_macros();
}
const string& name() const { return data_->name(); }
bool HasName() const { return data_->has_name(); }
const string& version() const { return data_->version(); }
const string& target() const { return data_->target(); }
const string& lang() const { return data_->lang(); }
const string& error_message() const { return data_->error_message(); }
const std::unordered_map<string, bool>& supported_predefined_macros() const {
return supported_predefined_macros_;
}
const std::unordered_map<string, int>& has_feature() const {
return has_feature_;
}
const std::unordered_map<string, int>& has_extension() const {
return has_extension_;
}
const std::unordered_map<string, int>& has_attribute() const {
return has_attribute_;
}
const std::unordered_map<string, int>& has_cpp_attribute() const {
return has_cpp_attribute_;
}
const std::unordered_map<string, int>& has_declspec_attribute() const {
return has_declspec_attribute_;
}
const std::unordered_map<string, int>& has_builtin() const {
return has_builtin_;
}
const std::vector<string>& additional_flags() const {
return additional_flags_;
}
bool HasAdditionalFlags() const { return !additional_flags_.empty(); }
const std::vector<SubprogramInfo>& subprograms() const {
return subprograms_;
}
time_t failed_at() const { return data_->failed_at(); }
time_t last_used_at() const;
void set_last_used_at(time_t t);
bool found() const { return data_->found(); }
bool IsSameCompiler(const CompilerInfo& ci) const {
return data_->target() == ci.data_->target()
&& data_->version() == ci.data_->version()
&& data_->lang() == ci.data_->lang()
&& data_->hash() == ci.data_->hash()
&& data_->real_compiler_path() == ci.data_->real_compiler_path();
}
const CompilerInfoData& data() const { return *data_; }
CompilerInfoData* get() { return data_.get(); }
private:
friend class CompilerInfoCacheTest;
void Init();
std::unique_ptr<CompilerInfoData> data_;
FileId local_compiler_id_;
// Real compiler's FileId if real_compiler_path != local_compiler_path.
// Otherwise, real_compiler_id is the same as local_compiler_id.
FileId real_compiler_id_;
std::vector<string> quote_include_paths_;
std::vector<string> cxx_system_include_paths_;
std::vector<string> system_include_paths_;
std::vector<string> system_framework_paths_;
// <macro name, hidden>.
// If it is hidden macro like __has_include__ in GCC 5, hidden is set.
std::unordered_map<string, bool> supported_predefined_macros_;
std::unordered_map<string, int> has_feature_;
std::unordered_map<string, int> has_extension_;
std::unordered_map<string, int> has_attribute_;
std::unordered_map<string, int> has_cpp_attribute_;
std::unordered_map<string, int> has_declspec_attribute_;
std::unordered_map<string, int> has_builtin_;
std::vector<string> additional_flags_;
// A list of subprograms specified by -B flag.
std::vector<SubprogramInfo> subprograms_;
mutable ReadWriteLock last_used_at_mu_;
DISALLOW_COPY_AND_ASSIGN(CompilerInfo);
};
class ScopedCompilerInfoState;
// CompilerInfoState contains CompilerInfo (created from local system) and
// disabled status (updated by response from remote).
// ref counted.
class CompilerInfoState {
public:
// Constructor creates with refcnt_==0.
// Before sharing it, caller should call Ref.
// Takes ownership of data.
explicit CompilerInfoState(std::unique_ptr<CompilerInfoData> data);
const CompilerInfo& info() const { return compiler_info_; }
// refcnt returns the current reference count.
// potentially race. when you get the value, actual refcnt may be updated.
// use be careful.
int refcnt() const;
// Returns if it has been disabled (e.g. compiler not found in backend)
// Potential race (i.e. even if caller gets disabled()==false, it will
// become true while checking input files or calling rpc), but it might
// be acceptable.
bool disabled() const;
string GetDisabledReason() const;
void SetDisabled(bool disabled, const string& disabled_reason);
void Use(const string& local_compiler_path,
const CompilerFlags& flags);
int used() const;
void UpdateLastUsedAt();
private:
friend class ScopedCompilerInfoState;
friend class CompilerInfoCache;
friend class CompilerInfoCacheTest;
~CompilerInfoState();
void Ref();
void Deref();
CompilerInfo compiler_info_;
mutable Lock mu_; // protects refcnt_, disabled_, disabled_reason_.
int refcnt_;
// When server side does not have the information about this compiler,
// it's disabled.
bool disabled_;
string disabled_reason_;
int used_;
DISALLOW_COPY_AND_ASSIGN(CompilerInfoState);
};
// ScopedCompilerInfoState manages lifecycle of CompilerInfoState.
// thread-unsafe.
//
// Initializes
// ScopedCompilerInfoState cis;
// cis.FillFromCompilerOutputs(...);
//
// share compiler_info_state with cis:
// ScopedCompilerInfoState state;
// state.reset(cis.get());
//
// ScopedCompilerInfoState state2(cis);
//
// transfer compiler_info_state from cis:
// ScopedCompilerInfoState state(std::move(cis));
class ScopedCompilerInfoState {
public:
ScopedCompilerInfoState() : state_(nullptr) {}
explicit ScopedCompilerInfoState(CompilerInfoState* state);
~ScopedCompilerInfoState();
ScopedCompilerInfoState(ScopedCompilerInfoState&& state) noexcept
: state_(std::move(state.state_)) {
state.state_ = nullptr;
}
ScopedCompilerInfoState& operator=(ScopedCompilerInfoState&& other) {
std::swap(state_, other.state_);
return *this;
}
CompilerInfoState* get() const { return state_; }
// reset derefs current state and refs given state.
void reset(CompilerInfoState* state);
// swap swaps state with other.
// useful to transfer state from other without modifying refcnt.
void swap(ScopedCompilerInfoState* other);
bool disabled() const;
string GetDisabledReason() const;
private:
CompilerInfoState* state_;
DISALLOW_COPY_AND_ASSIGN(ScopedCompilerInfoState);
};
} // namespace devtools_goma
#endif // DEVTOOLS_GOMA_CLIENT_COMPILER_INFO_H_