refactor: incremental update for compilation database and introduce query toolchain (#311)
This commit is contained in:
4
.github/workflows/cmake.yml
vendored
4
.github/workflows/cmake.yml
vendored
@@ -103,5 +103,9 @@ jobs:
|
||||
EXE_EXT=".exe"
|
||||
fi
|
||||
|
||||
if [[ "${{ runner.os }}" == "macOS" ]]; then
|
||||
export PATH="/opt/homebrew/opt/llvm@20/bin:/opt/homebrew/opt/lld@20/bin:$PATH"
|
||||
fi
|
||||
|
||||
./build/bin/unit_tests${EXE_EXT} --test-dir="./tests/data"
|
||||
uv run pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice${EXE_EXT}
|
||||
|
||||
@@ -27,8 +27,12 @@ include("${PROJECT_SOURCE_DIR}/cmake/package.cmake")
|
||||
|
||||
add_library(clice_options INTERFACE)
|
||||
|
||||
if(CLICE_ENABLE_TEST)
|
||||
target_compile_definitions(clice_options INTERFACE CLICE_ENABLE_TEST=1)
|
||||
endif()
|
||||
|
||||
if(CLICE_CI_ENVIRONMENT)
|
||||
target_compile_definitions(clice_options INTERFACE CLICE_CI_ENVIRONMENT)
|
||||
target_compile_definitions(clice_options INTERFACE CLICE_CI_ENVIRONMENT=1)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
|
||||
10
bin/clice.cc
10
bin/clice.cc
@@ -94,11 +94,11 @@ int main(int argc, const char** argv) {
|
||||
logging::stderr_logger("clice", logging::options);
|
||||
|
||||
if(auto result = fs::init_resource_dir(argv[0]); !result) {
|
||||
LOGGING_FATAL("Cannot find default resource directory, because {}", result.error());
|
||||
LOG_FATAL("Cannot find default resource directory, because {}", result.error());
|
||||
}
|
||||
|
||||
for(int i = 0; i < argc; ++i) {
|
||||
LOGGING_INFO("argv[{}] = {}", i, argv[i]);
|
||||
LOG_INFO("argv[{}] = {}", i, argv[i]);
|
||||
}
|
||||
|
||||
async::init();
|
||||
@@ -112,13 +112,13 @@ int main(int argc, const char** argv) {
|
||||
switch(mode) {
|
||||
case Mode::Pipe: {
|
||||
async::net::listen(loop);
|
||||
LOGGING_INFO("Server starts listening on stdin/stdout");
|
||||
LOG_INFO("Server starts listening on stdin/stdout");
|
||||
break;
|
||||
}
|
||||
|
||||
case Mode::Socket: {
|
||||
async::net::listen(host.c_str(), port, loop);
|
||||
LOGGING_INFO("Server starts listening on {}:{}", host.getValue(), port.getValue());
|
||||
LOG_INFO("Server starts listening on {}:{}", host.getValue(), port.getValue());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ int main(int argc, const char** argv) {
|
||||
|
||||
async::run();
|
||||
|
||||
LOGGING_INFO("clice exit normally!");
|
||||
LOG_INFO("clice exit normally!");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ struct CommandOptions {
|
||||
bool resource_dir = false;
|
||||
|
||||
/// Query the compiler driver for additional information, such as system includes and target.
|
||||
bool query_driver = false;
|
||||
bool query_toolchain = false;
|
||||
|
||||
/// Suppress the warning log if failed to query driver info.
|
||||
/// Set true in unittests to avoid cluttering test output.
|
||||
@@ -35,36 +35,34 @@ struct CommandOptions {
|
||||
};
|
||||
|
||||
enum class UpdateKind : std::uint8_t {
|
||||
Unchange,
|
||||
Create,
|
||||
Update,
|
||||
Delete,
|
||||
};
|
||||
|
||||
struct DriverInfo {
|
||||
/// The target of this driver.
|
||||
llvm::StringRef target;
|
||||
|
||||
/// The default system includes of this driver.
|
||||
llvm::ArrayRef<const char*> system_includes;
|
||||
Unchanged,
|
||||
Inserted,
|
||||
Deleted,
|
||||
};
|
||||
|
||||
struct UpdateInfo {
|
||||
/// The kind of update.
|
||||
UpdateKind kind;
|
||||
|
||||
llvm::StringRef file;
|
||||
/// The updated file.
|
||||
std::uint32_t path_id;
|
||||
|
||||
/// The compilation context of this file command, which could
|
||||
/// be used to identity the same file with different compilation
|
||||
/// contexts.
|
||||
const void* context;
|
||||
};
|
||||
|
||||
struct LookupInfo {
|
||||
struct CompilationContext {
|
||||
/// The working directory of compilation.
|
||||
llvm::StringRef directory;
|
||||
|
||||
/// The compilation arguments.
|
||||
std::vector<const char*> arguments;
|
||||
|
||||
/// The include arguments indices in the arguments list.
|
||||
std::vector<std::uint32_t> include_indices;
|
||||
};
|
||||
|
||||
std::string print_argv(llvm::ArrayRef<const char*> args);
|
||||
|
||||
class CompilationDatabase {
|
||||
public:
|
||||
CompilationDatabase();
|
||||
@@ -79,45 +77,48 @@ public:
|
||||
|
||||
~CompilationDatabase();
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
|
||||
using Self = CompilationDatabase;
|
||||
|
||||
public:
|
||||
/// Read the compilation database on the give file and return the
|
||||
/// incremental update infos.
|
||||
std::vector<UpdateInfo> load_compile_database(llvm::StringRef file);
|
||||
|
||||
/// Lookup the compilation context of specific file. If the context
|
||||
/// param is provided, we will return the compilation context corresponding
|
||||
/// to the handle. Otherwise we just return the first one(if the file have)
|
||||
/// multiple compilation contexts.
|
||||
CompilationContext lookup(llvm::StringRef file,
|
||||
const CommandOptions& options = {},
|
||||
const void* context = nullptr);
|
||||
|
||||
/// TODO: list all compilation context of the file, this is useful to show
|
||||
/// all contexts and let user choose one.
|
||||
/// std::vector<CompilationContext> fetch_all(llvm::StringRef file);
|
||||
|
||||
/// Get an the option for specific argument.
|
||||
static std::optional<std::uint32_t> get_option_id(llvm::StringRef argument);
|
||||
|
||||
auto save_string(llvm::StringRef string) -> llvm::StringRef;
|
||||
|
||||
/// Query the compiler driver and return its driver info.
|
||||
auto query_driver(llvm::StringRef driver)
|
||||
-> std::expected<DriverInfo, toolchain::QueryDriverError>;
|
||||
|
||||
/// Update with arguments.
|
||||
auto update_command(llvm::StringRef directory,
|
||||
llvm::StringRef file,
|
||||
llvm::ArrayRef<const char*> arguments) -> UpdateInfo;
|
||||
|
||||
/// Update with full command.
|
||||
auto update_command(llvm::StringRef directory, llvm::StringRef file, llvm::StringRef command)
|
||||
-> UpdateInfo;
|
||||
|
||||
/// Update commands from json file and return all updated file.
|
||||
auto load_commands(llvm::StringRef json_content, llvm::StringRef workspace)
|
||||
-> std::expected<std::vector<UpdateInfo>, std::string>;
|
||||
|
||||
/// Load compile commands from given directories. If no valid commands are found,
|
||||
/// search recursively from the workspace directory.
|
||||
auto load_compile_database(llvm::ArrayRef<std::string> compile_commands_dirs,
|
||||
llvm::StringRef workspace) -> void;
|
||||
|
||||
/// Get compile command from database. `file` should has relative path of workspace.
|
||||
auto lookup(llvm::StringRef file, CommandOptions options = {}) -> LookupInfo;
|
||||
|
||||
/// FIXME: bad interface design ...
|
||||
std::vector<const char*> files();
|
||||
|
||||
/// FIXME: remove this api?
|
||||
auto save_string(llvm::StringRef string) -> llvm::StringRef;
|
||||
|
||||
#ifdef CLICE_ENABLE_TEST
|
||||
|
||||
void add_command(llvm::StringRef directory,
|
||||
llvm::StringRef file,
|
||||
llvm::ArrayRef<const char*> arguments);
|
||||
|
||||
void add_command(llvm::StringRef directory, llvm::StringRef file, llvm::StringRef command);
|
||||
|
||||
/// FIXME: remove this
|
||||
/// Update commands from json file and return all updated file.
|
||||
std::expected<std::vector<UpdateInfo>, std::string> load_commands(llvm::StringRef json_content,
|
||||
llvm::StringRef workspace);
|
||||
#endif
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> self;
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ struct CompilationParams {
|
||||
|
||||
std::string directory;
|
||||
|
||||
bool arguments_from_database = false;
|
||||
|
||||
/// Responsible for storing the arguments.
|
||||
std::vector<const char*> arguments;
|
||||
|
||||
|
||||
@@ -1,51 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <expected>
|
||||
#include "Support/Enum.h"
|
||||
#include "Support/Format.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
|
||||
namespace clice::toolchain {
|
||||
|
||||
struct QueryDriverError {
|
||||
struct ErrorKind : refl::Enum<ErrorKind> {
|
||||
enum Kind : std::uint8_t {
|
||||
NotFoundInPATH,
|
||||
NotImplemented,
|
||||
FailToCreateTempFile,
|
||||
InvokeDriverFail,
|
||||
OutputFileNotReadable,
|
||||
InvalidOutputFormat,
|
||||
};
|
||||
|
||||
using Enum::Enum;
|
||||
};
|
||||
|
||||
ErrorKind kind;
|
||||
std::string detail;
|
||||
enum class CompilerFamily {
|
||||
Unknown,
|
||||
GCC, // Covers gcc, g++, cc, c++, and versioned/arch variants
|
||||
Clang, // Covers clang, clang++, and versioned variants (excluding clang-cl)
|
||||
MSVC, // Covers cl
|
||||
ClangCL, // Covers clang-cl explicitly
|
||||
NVCC, // Covers nvcc
|
||||
Intel, // Covers icc, icpc, icx, dpcpp
|
||||
Zig, // Covers zig cc / zig c++ (assumed GCC/Clang compatible for query)
|
||||
};
|
||||
|
||||
struct QueryResult {
|
||||
std::string target;
|
||||
llvm::SmallVector<std::string, 8> includes;
|
||||
struct QueryParams {
|
||||
llvm::StringRef file;
|
||||
llvm::StringRef directory;
|
||||
llvm::ArrayRef<const char*> arguments;
|
||||
llvm::function_ref<const char*(const char*)> callback;
|
||||
};
|
||||
|
||||
enum class Kind {};
|
||||
/// Query the toolchain info and return the full arguments, the returned arguments should be
|
||||
/// converted to `clang::CompilerInvocation::CreateFromArgs` directly.
|
||||
std::vector<const char*> query_toolchain(const QueryParams& params);
|
||||
|
||||
struct Toolchain {};
|
||||
CompilerFamily driver_family(llvm::StringRef driver);
|
||||
|
||||
/// Query toolchain info according to the original compilation arguments.
|
||||
Toolchain query_toolchain(llvm::ArrayRef<const char*> arguments);
|
||||
/// Query g++ or mingw toolchain info. We detect the target and corresponding
|
||||
/// gcc toolchain install path as default behavior.
|
||||
std::vector<const char*> query_gcc_toolchain(const QueryParams& params);
|
||||
|
||||
auto query_driver(llvm::StringRef driver) -> std::expected<QueryResult, QueryDriverError>;
|
||||
/// Query clang++ or any clang based toolchain, e.g. zig cc/c++. We query
|
||||
/// the full cc1 command of clang toolchain as default.
|
||||
/// TODO: Is armclang also compatible?
|
||||
std::vector<const char*> query_clang_toolchain(const QueryParams& params);
|
||||
|
||||
/// Query the msvc or clang-cl toolchain, default behavior only adds the
|
||||
/// target and includes info.
|
||||
std::vector<const char*> query_msvc_toolchain(const QueryParams& params);
|
||||
|
||||
/// FIXME: To be implemented
|
||||
std::vector<const char*> query_nvcc_toolchain(const QueryParams& params);
|
||||
|
||||
} // namespace clice::toolchain
|
||||
|
||||
template <>
|
||||
struct std::formatter<clice::toolchain::QueryDriverError> : std::formatter<std::string_view> {
|
||||
template <typename FormatContext>
|
||||
auto format(const clice::toolchain::QueryDriverError& e, FormatContext& ctx) const {
|
||||
return std::format_to(ctx.out(), "{} {}", e.kind.name(), e.detail);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -50,6 +50,8 @@ inline std::expected<void, std::string> init_resource_dir(llvm::StringRef execut
|
||||
return std::expected<void, std::string>();
|
||||
}
|
||||
|
||||
using llvm::sys::fs::createTemporaryFile;
|
||||
|
||||
inline std::expected<std::string, std::error_code> createTemporaryFile(llvm::StringRef prefix,
|
||||
llvm::StringRef suffix) {
|
||||
llvm::SmallString<128> path;
|
||||
|
||||
@@ -92,14 +92,26 @@ void critical [[noreturn]] (logging_format<Args...> fmt, Args&&... args) {
|
||||
|
||||
} // namespace clice::logging
|
||||
|
||||
#define LOGGING_MESSAGE(name, fmt, ...) \
|
||||
#define LOG_MESSAGE(name, fmt, ...) \
|
||||
if(clice::logging::options.level <= clice::logging::Level::name) { \
|
||||
clice::logging::name(fmt __VA_OPT__(, ) __VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define LOGGING_TRACE(fmt, ...) LOGGING_MESSAGE(trace, fmt, __VA_ARGS__)
|
||||
#define LOGGING_DEBUG(fmt, ...) LOGGING_MESSAGE(debug, fmt, __VA_ARGS__)
|
||||
#define LOGGING_INFO(fmt, ...) LOGGING_MESSAGE(info, fmt, __VA_ARGS__)
|
||||
#define LOGGING_WARN(fmt, ...) LOGGING_MESSAGE(warn, fmt, __VA_ARGS__)
|
||||
#define LOGGING_ERROR(fmt, ...) LOGGING_MESSAGE(err, fmt, __VA_ARGS__)
|
||||
#define LOGGING_FATAL(fmt, ...) clice::logging::critical(fmt __VA_OPT__(, ) __VA_ARGS__);
|
||||
#define LOG_TRACE(fmt, ...) LOG_MESSAGE(trace, fmt, __VA_ARGS__)
|
||||
#define LOG_DEBUG(fmt, ...) LOG_MESSAGE(debug, fmt, __VA_ARGS__)
|
||||
#define LOG_INFO(fmt, ...) LOG_MESSAGE(info, fmt, __VA_ARGS__)
|
||||
#define LOG_WARN(fmt, ...) LOG_MESSAGE(warn, fmt, __VA_ARGS__)
|
||||
#define LOG_ERROR(fmt, ...) LOG_MESSAGE(err, fmt, __VA_ARGS__)
|
||||
#define LOG_FATAL(fmt, ...) clice::logging::critical(fmt __VA_OPT__(, ) __VA_ARGS__);
|
||||
|
||||
#define LOG_MESSAGE_RET(ret, name, fmt, ...) \
|
||||
do { \
|
||||
LOG_MESSAGE(name, fmt, __VA_ARGS__); \
|
||||
return ret; \
|
||||
} while(0);
|
||||
|
||||
#define LOG_TRACE_RET(ret, fmt, ...) LOG_MESSAGE_RET(ret, trace, fmt, __VA_ARGS__)
|
||||
#define LOG_DEBUG_RET(ret, fmt, ...) LOG_MESSAGE_RET(ret, debug, fmt, __VA_ARGS__)
|
||||
#define LOG_INFO_RET(ret, fmt, ...) LOG_MESSAGE_RET(ret, info, fmt, __VA_ARGS__)
|
||||
#define LOG_WARN_RET(ret, fmt, ...) LOG_MESSAGE_RET(ret, warn, fmt, __VA_ARGS__)
|
||||
#define LOG_ERROR_RET(ret, fmt, ...) LOG_MESSAGE_RET(ret, err, fmt, __VA_ARGS__)
|
||||
|
||||
224
include/Support/ObjectPool.h
Normal file
224
include/Support/ObjectPool.h
Normal file
@@ -0,0 +1,224 @@
|
||||
#pragma once
|
||||
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/Support/Allocator.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
class StringSet {
|
||||
public:
|
||||
using ID = std::uint32_t;
|
||||
|
||||
StringSet(llvm::BumpPtrAllocator& allocator) : allocator(allocator) {
|
||||
strings.emplace_back();
|
||||
}
|
||||
|
||||
StringSet(const StringSet&) = delete;
|
||||
|
||||
StringSet(StringSet&&) = delete;
|
||||
|
||||
StringSet& operator= (const StringSet&) = delete;
|
||||
|
||||
StringSet& operator= (StringSet&&) = delete;
|
||||
|
||||
~StringSet() = default;
|
||||
|
||||
ID get(llvm::StringRef s) {
|
||||
if(s.empty()) {
|
||||
return ID(0);
|
||||
}
|
||||
|
||||
auto [it, success] = cache.try_emplace(s, ID(0));
|
||||
if(!success) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
const auto size = s.size();
|
||||
auto p = allocator.Allocate<char>(size + 1);
|
||||
std::memcpy(p, s.data(), size);
|
||||
p[size] = '\0';
|
||||
|
||||
it->first = llvm::StringRef(p, size);
|
||||
it->second = ID(strings.size());
|
||||
strings.emplace_back(it->first);
|
||||
return it->second;
|
||||
}
|
||||
|
||||
llvm::StringRef get(ID id) {
|
||||
assert(id < strings.size());
|
||||
return strings[id];
|
||||
}
|
||||
|
||||
llvm::StringRef save(llvm::StringRef s) {
|
||||
return get(get(s));
|
||||
}
|
||||
|
||||
private:
|
||||
llvm::BumpPtrAllocator& allocator;
|
||||
|
||||
std::vector<llvm::StringRef> strings;
|
||||
|
||||
llvm::DenseMap<llvm::StringRef, ID> cache;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct object_ptr {
|
||||
T* ptr = nullptr;
|
||||
|
||||
object_ptr() noexcept = default;
|
||||
|
||||
object_ptr(std::nullptr_t) noexcept : ptr(nullptr) {}
|
||||
|
||||
explicit object_ptr(T* p) noexcept : ptr(p) {}
|
||||
|
||||
T& operator* () const noexcept {
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
T* operator->() const noexcept {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept {
|
||||
return ptr != nullptr;
|
||||
}
|
||||
|
||||
std::strong_ordering operator<=> (const object_ptr&) const = default;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
object_ptr(T*) -> object_ptr<T>;
|
||||
|
||||
template <typename T>
|
||||
class ObjectSet {
|
||||
public:
|
||||
using ID = std::uint32_t;
|
||||
|
||||
ObjectSet(llvm::BumpPtrAllocator& allocator) : allocator(allocator) {}
|
||||
|
||||
ObjectSet(const ObjectSet&) = delete;
|
||||
|
||||
ObjectSet(ObjectSet&&) = delete;
|
||||
|
||||
ObjectSet& operator= (const ObjectSet&) = delete;
|
||||
|
||||
ObjectSet& operator= (ObjectSet&&) = delete;
|
||||
|
||||
~ObjectSet() {
|
||||
if constexpr(!std::is_trivially_destructible_v<T>) {
|
||||
for(auto object: objects) {
|
||||
if(object) {
|
||||
std::destroy_at(object.ptr);
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& [object, _]: removed) {
|
||||
if(object) {
|
||||
std::destroy_at(object.ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ID get(const T& object) {
|
||||
auto [it, success] = cache.try_emplace(object_ptr(const_cast<T*>(&object)), ID(0));
|
||||
if(!success) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
if(!removed.empty()) [[unlikely]] {
|
||||
auto [o, id] = removed.pop_back_val();
|
||||
|
||||
/// Reuse the old memory
|
||||
std::destroy_at(o.ptr);
|
||||
new (o.ptr) T(object);
|
||||
|
||||
it->first = o;
|
||||
it->second = id;
|
||||
|
||||
/// Resume the object id.
|
||||
objects[id] = o;
|
||||
} else {
|
||||
/// Alloc the new memory.
|
||||
auto p = allocator.Allocate<T>(1);
|
||||
p = new (p) T(object);
|
||||
|
||||
it->first = object_ptr<T>{p};
|
||||
it->second = ID(objects.size());
|
||||
|
||||
objects.emplace_back(p);
|
||||
}
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
object_ptr<T> get(ID id) {
|
||||
assert(id < objects.size());
|
||||
return objects[id];
|
||||
}
|
||||
|
||||
object_ptr<T> save(const T& object) {
|
||||
return this->get(this->get(object));
|
||||
}
|
||||
|
||||
void remove(object_ptr<T> object) {
|
||||
auto it = cache.find(object_ptr<T>(object.ptr));
|
||||
if(it == cache.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto id = it->second;
|
||||
removed.emplace_back(object, id);
|
||||
cache.erase(it);
|
||||
objects[id] = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
llvm::BumpPtrAllocator& allocator;
|
||||
|
||||
std::vector<object_ptr<T>> objects;
|
||||
|
||||
llvm::SmallVector<std::pair<object_ptr<T>, ID>> removed;
|
||||
|
||||
llvm::DenseMap<object_ptr<T>, ID> cache;
|
||||
};
|
||||
|
||||
} // namespace clice
|
||||
|
||||
namespace llvm {
|
||||
|
||||
template <typename T>
|
||||
struct DenseMapInfo<clice::object_ptr<T>> {
|
||||
using U = std::remove_cvref_t<T>;
|
||||
using O = clice::object_ptr<T>;
|
||||
|
||||
inline static O getEmptyKey() {
|
||||
return O(DenseMapInfo<U*>::getEmptyKey());
|
||||
}
|
||||
|
||||
inline static O getTombstoneKey() {
|
||||
return O(DenseMapInfo<U*>::getTombstoneKey());
|
||||
}
|
||||
|
||||
inline static unsigned getHashValue(O value) {
|
||||
return DenseMapInfo<U>::getHashValue(*value);
|
||||
}
|
||||
|
||||
inline static bool isEqual(O lhs, O rhs) {
|
||||
if(lhs == rhs) {
|
||||
return true;
|
||||
};
|
||||
|
||||
const O Empty = getEmptyKey();
|
||||
const O Tombstone = getTombstoneKey();
|
||||
|
||||
if(lhs == Empty || rhs == Empty || lhs == Tombstone || rhs == Tombstone) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return DenseMapInfo<U>::isEqual(*lhs, *rhs);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace llvm
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Protocol/Protocol.h"
|
||||
#include "Compiler/Command.h"
|
||||
#include "Compiler/Compilation.h"
|
||||
#include "Support/Logging.h"
|
||||
|
||||
namespace clice::testing {
|
||||
|
||||
@@ -31,157 +32,23 @@ struct Tester {
|
||||
sources.add_sources(content);
|
||||
}
|
||||
|
||||
void prepare(llvm::StringRef standard = "-std=c++20") {
|
||||
auto command = std::format("clang++ {} {} -fms-extensions", standard, src_path);
|
||||
void prepare(llvm::StringRef standard = "-std=c++20");
|
||||
|
||||
database.update_command("fake", src_path, command);
|
||||
params.kind = CompilationUnit::Content;
|
||||
bool compile(llvm::StringRef standard = "-std=c++20");
|
||||
|
||||
CommandOptions options;
|
||||
options.resource_dir = true;
|
||||
options.query_driver = true;
|
||||
options.suppress_logging = true;
|
||||
params.arguments = database.lookup(src_path, options).arguments;
|
||||
|
||||
for(auto& [file, source]: sources.all_files) {
|
||||
if(file == src_path) {
|
||||
params.add_remapped_file(file, source.content);
|
||||
} else {
|
||||
/// FIXME: This is a workaround.
|
||||
std::string path = path::is_absolute(file) ? file.str() : path::join(".", file);
|
||||
params.add_remapped_file(path, source.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool compile(llvm::StringRef standard = "-std=c++20") {
|
||||
prepare(standard);
|
||||
|
||||
auto info = clice::compile(params);
|
||||
if(!info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->unit.emplace(std::move(*info));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool compile_with_pch(llvm::StringRef standard = "-std=c++20") {
|
||||
params.diagnostics = std::make_shared<std::vector<Diagnostic>>();
|
||||
auto command = std::format("clang++ {} {} -fms-extensions", standard, src_path);
|
||||
|
||||
database.update_command("fake", src_path, command);
|
||||
params.kind = CompilationUnit::Preamble;
|
||||
|
||||
CommandOptions options;
|
||||
options.resource_dir = true;
|
||||
options.query_driver = true;
|
||||
options.suppress_logging = true;
|
||||
params.arguments = database.lookup(src_path, options).arguments;
|
||||
|
||||
auto path = fs::createTemporaryFile("clice", "pch");
|
||||
if(!path) {
|
||||
llvm::outs() << path.error().message() << "\n";
|
||||
}
|
||||
|
||||
/// Build PCH
|
||||
params.output_file = *path;
|
||||
|
||||
for(auto& [file, source]: sources.all_files) {
|
||||
if(file == src_path) {
|
||||
auto bound = compute_preamble_bound(source.content);
|
||||
params.add_remapped_file(file, source.content, bound);
|
||||
} else {
|
||||
/// FIXME: This is a workaround.
|
||||
std::string path = path::is_absolute(file) ? file.str() : path::join(".", file);
|
||||
params.add_remapped_file(path, source.content);
|
||||
}
|
||||
}
|
||||
|
||||
PCHInfo info;
|
||||
{
|
||||
auto unit = clice::compile(params, info);
|
||||
if(!unit) {
|
||||
llvm::outs() << unit.error() << "\n";
|
||||
for(auto& diag: *params.diagnostics) {
|
||||
std::println("{}", diag.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Build AST
|
||||
params.output_file.clear();
|
||||
params.kind = CompilationUnit::Content;
|
||||
params.pch = {info.path, info.preamble.size()};
|
||||
for(auto& [file, source]: sources.all_files) {
|
||||
if(file == src_path) {
|
||||
params.add_remapped_file(file, source.content);
|
||||
} else {
|
||||
/// FIXME: This is a workaround.
|
||||
std::string path = path::is_absolute(file) ? file.str() : path::join(".", file);
|
||||
params.add_remapped_file(path, source.content);
|
||||
}
|
||||
}
|
||||
|
||||
auto unit = clice::compile(params);
|
||||
if(!unit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->unit.emplace(std::move(*unit));
|
||||
return true;
|
||||
}
|
||||
bool compile_with_pch(llvm::StringRef standard = "-std=c++20");
|
||||
|
||||
std::uint32_t operator[] (llvm::StringRef file, llvm::StringRef pos) {
|
||||
return sources.all_files.lookup(file).offsets.lookup(pos);
|
||||
}
|
||||
|
||||
std::uint32_t point(llvm::StringRef name = "", llvm::StringRef file = "") {
|
||||
if(file.empty()) {
|
||||
file = src_path;
|
||||
}
|
||||
std::uint32_t point(llvm::StringRef name = "", llvm::StringRef file = "");
|
||||
|
||||
auto& offsets = sources.all_files[file].offsets;
|
||||
if(name.empty()) {
|
||||
assert(offsets.size() == 1);
|
||||
return offsets.begin()->second;
|
||||
} else {
|
||||
assert(offsets.contains(name));
|
||||
return offsets.lookup(name);
|
||||
}
|
||||
}
|
||||
llvm::ArrayRef<std::uint32_t> nameless_points(llvm::StringRef file = "");
|
||||
|
||||
llvm::ArrayRef<std::uint32_t> nameless_points(llvm::StringRef file = "") {
|
||||
if(file.empty()) {
|
||||
file = src_path;
|
||||
}
|
||||
LocalSourceRange range(llvm::StringRef name = "", llvm::StringRef file = "");
|
||||
|
||||
return sources.all_files[file].nameless_offsets;
|
||||
}
|
||||
|
||||
LocalSourceRange range(llvm::StringRef name = "", llvm::StringRef file = "") {
|
||||
if(file.empty()) {
|
||||
file = src_path;
|
||||
}
|
||||
|
||||
auto& ranges = sources.all_files[file].ranges;
|
||||
if(name.empty()) {
|
||||
assert(ranges.size() == 1);
|
||||
return ranges.begin()->second;
|
||||
} else {
|
||||
assert(ranges.contains(name));
|
||||
return ranges.lookup(name);
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
params = CompilationParams();
|
||||
database = CompilationDatabase();
|
||||
unit.reset();
|
||||
sources.all_files.clear();
|
||||
src_path.clear();
|
||||
}
|
||||
void clear();
|
||||
};
|
||||
|
||||
} // namespace clice::testing
|
||||
|
||||
@@ -948,10 +948,10 @@ private:
|
||||
}
|
||||
|
||||
if(!checker.may_hit(S)) {
|
||||
LOGGING_DEBUG("{2}skip: {0} {1}",
|
||||
print_node_to_string(N, print_policy),
|
||||
S.printToString(SM),
|
||||
indent());
|
||||
LOG_DEBUG("{2}skip: {0} {1}",
|
||||
print_node_to_string(N, print_policy),
|
||||
S.printToString(SM),
|
||||
indent());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -971,10 +971,10 @@ private:
|
||||
// Performs early hit detection for some nodes (on the earlySourceRange).
|
||||
void push(clang::DynTypedNode node) {
|
||||
clang::SourceRange Early = early_source_range(node);
|
||||
LOGGING_DEBUG("{2}push: {0} {1}",
|
||||
print_node_to_string(node, print_policy),
|
||||
node.getSourceRange().printToString(SM),
|
||||
indent());
|
||||
LOG_DEBUG("{2}push: {0} {1}",
|
||||
print_node_to_string(node, print_policy),
|
||||
node.getSourceRange().printToString(SM),
|
||||
indent());
|
||||
nodes.emplace_back();
|
||||
nodes.back().data = std::move(node);
|
||||
nodes.back().parent = stack.top();
|
||||
@@ -987,7 +987,7 @@ private:
|
||||
// Performs primary hit detection.
|
||||
void pop() {
|
||||
Node& N = *stack.top();
|
||||
LOGGING_DEBUG("{1}pop: {0}", print_node_to_string(N.data, print_policy), indent(-1));
|
||||
LOG_DEBUG("{1}pop: {0}", print_node_to_string(N.data, print_policy), indent(-1));
|
||||
claim_tokens_for(N.data, N.selected);
|
||||
if(N.selected == no_tokens) {
|
||||
N.selected = SelectionTree::Unselected;
|
||||
@@ -1118,7 +1118,7 @@ private:
|
||||
}
|
||||
|
||||
if(result && result != no_tokens) {
|
||||
LOGGING_DEBUG("{1}hit selection: {0}", S.printToString(SM), indent());
|
||||
LOG_DEBUG("{1}hit selection: {0}", S.printToString(SM), indent());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1243,9 +1243,9 @@ SelectionTree::SelectionTree(CompilationUnit& unit, LocalSourceRange range) :
|
||||
print_policy.IncludeNewlines = false;
|
||||
auto [begin, end] = range;
|
||||
|
||||
LOGGING_DEBUG("Computing selection for {0}",
|
||||
clang::SourceRange(SM.getComposedLoc(fid, begin), SM.getComposedLoc(fid, end))
|
||||
.printToString(SM));
|
||||
LOG_DEBUG("Computing selection for {0}",
|
||||
clang::SourceRange(SM.getComposedLoc(fid, begin), SM.getComposedLoc(fid, end))
|
||||
.printToString(SM));
|
||||
|
||||
nodes = SelectionVisitor::collect(unit, print_policy, range, fid);
|
||||
m_root = nodes.empty() ? nullptr : &nodes.front();
|
||||
|
||||
@@ -122,7 +122,7 @@ handle::~handle() {
|
||||
uv_fs_t request;
|
||||
int error = uv_fs_close(async::loop, &request, file, nullptr);
|
||||
if(error < 0) {
|
||||
LOGGING_WARN("Failed to close file: {}", uv_strerror(error));
|
||||
LOG_WARN("Failed to close file: {}", uv_strerror(error));
|
||||
}
|
||||
uv_fs_req_cleanup(&request);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
|
||||
|
||||
/// If an error occurred while reading, we can't continue.
|
||||
if(nread < 0) [[unlikely]] {
|
||||
LOGGING_FATAL("An error occurred while reading: {0}", uv_strerror(nread));
|
||||
LOG_FATAL("An error occurred while reading: {0}", uv_strerror(nread));
|
||||
}
|
||||
|
||||
/// We have at most one connection and use default event loop. So there is no data race
|
||||
@@ -57,7 +57,7 @@ void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
|
||||
task.dispose();
|
||||
} else {
|
||||
/// If the message is invalid, we can't continue.
|
||||
LOGGING_FATAL("Unexpected JSON input: {0}", result);
|
||||
LOG_FATAL("Unexpected JSON input: {0}", result);
|
||||
}
|
||||
|
||||
/// Remove the processed message from the buffer.
|
||||
@@ -130,7 +130,7 @@ struct write {
|
||||
|
||||
uv_write(&req, writer, buf, 2, [](uv_write_t* req, int status) {
|
||||
if(status < 0) {
|
||||
LOGGING_FATAL("An error occurred while writing: {0}", uv_strerror(status));
|
||||
LOG_FATAL("An error occurred while writing: {0}", uv_strerror(status));
|
||||
}
|
||||
|
||||
auto& awaiter = uv_cast<struct write>(req);
|
||||
|
||||
@@ -29,11 +29,8 @@ const std::error_category& category() {
|
||||
/// Use source_location to log the file, line, and function name where the error occurred.
|
||||
void uv_check_result(const int result, const std::source_location location) {
|
||||
if(result < 0) {
|
||||
LOGGING_WARN("libuv error: {}", uv_strerror(result));
|
||||
LOGGING_WARN("At {}:{}:{}",
|
||||
location.file_name(),
|
||||
location.line(),
|
||||
location.function_name());
|
||||
LOG_WARN("libuv error: {}", uv_strerror(result));
|
||||
LOG_WARN("At {}:{}:{}", location.file_name(), location.line(), location.function_name());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@
|
||||
#include "clang/Lex/PreprocessorOptions.h"
|
||||
#include "clang/Frontend/TextDiagnosticPrinter.h"
|
||||
#include "clang/Frontend/MultiplexConsumer.h"
|
||||
#include "Support/Logging.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
@@ -99,20 +100,40 @@ private:
|
||||
auto create_invocation(CompilationParams& params,
|
||||
llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine>& diagnostic_engine)
|
||||
-> std::unique_ptr<clang::CompilerInvocation> {
|
||||
if(params.arguments.empty()) {
|
||||
LOG_ERROR_RET(nullptr, "Fail to create invocation: empty argument list from database");
|
||||
}
|
||||
|
||||
/// Create clang invocation.
|
||||
clang::CreateInvocationOptions options = {
|
||||
.Diags = diagnostic_engine,
|
||||
.VFS = params.vfs,
|
||||
std::unique_ptr<clang::CompilerInvocation> invocation;
|
||||
|
||||
/// Avoid replacing -include with -include-pch, also
|
||||
/// see https://github.com/clangd/clangd/issues/856.
|
||||
.ProbePrecompiled = false,
|
||||
};
|
||||
/// Arguments from compilation database are already cc1
|
||||
if(params.arguments_from_database) {
|
||||
invocation = std::make_unique<clang::CompilerInvocation>();
|
||||
if(!clang::CompilerInvocation::CreateFromArgs(*invocation,
|
||||
llvm::ArrayRef(params.arguments).drop_front(),
|
||||
*diagnostic_engine,
|
||||
params.arguments[0])) {
|
||||
LOG_ERROR_RET(nullptr,
|
||||
" Fail to create invocation, arguments list is: {}",
|
||||
print_argv(params.arguments));
|
||||
}
|
||||
} else {
|
||||
/// Create clang invocation.
|
||||
clang::CreateInvocationOptions options = {
|
||||
.Diags = diagnostic_engine,
|
||||
.VFS = params.vfs,
|
||||
|
||||
auto invocation = clang::createInvocation(params.arguments, options);
|
||||
if(!invocation) {
|
||||
return nullptr;
|
||||
/// Avoid replacing -include with -include-pch, also
|
||||
/// see https://github.com/clangd/clangd/issues/856.
|
||||
.ProbePrecompiled = false,
|
||||
};
|
||||
|
||||
invocation = clang::createInvocation(params.arguments, options);
|
||||
if(!invocation) {
|
||||
LOG_ERROR_RET(nullptr,
|
||||
" Fail to create invocation, arguments list is: {}",
|
||||
print_argv(params.arguments));
|
||||
}
|
||||
}
|
||||
|
||||
auto& pp_opts = invocation->getPreprocessorOpts();
|
||||
@@ -336,7 +357,10 @@ CompilationResult preprocess(CompilationParams& params) {
|
||||
}
|
||||
|
||||
CompilationResult compile(CompilationParams& params) {
|
||||
return run_clang<clang::SyntaxOnlyAction>(params);
|
||||
return run_clang<clang::SyntaxOnlyAction>(params, [](clang::CompilerInstance& instance) {
|
||||
/// Make sure the output file is empty.
|
||||
instance.getFrontendOpts().OutputFile.clear();
|
||||
});
|
||||
}
|
||||
|
||||
CompilationResult compile(CompilationParams& params, PCHInfo& out) {
|
||||
|
||||
@@ -334,11 +334,11 @@ std::unique_ptr<ClangTidyChecker> configure(clang::CompilerInstance& instance,
|
||||
return nullptr;
|
||||
}
|
||||
auto file_name = input.getFile();
|
||||
LOGGING_INFO("Tidy configure file: {}", file_name);
|
||||
LOG_INFO("Tidy configure file: {}", file_name);
|
||||
|
||||
tidy::ClangTidyOptions opts = create_options();
|
||||
if(opts.Checks) {
|
||||
LOGGING_INFO("Tidy configure checks: {}", *opts.Checks);
|
||||
LOG_INFO("Tidy configure checks: {}", *opts.Checks);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -391,7 +391,7 @@ std::unique_ptr<ClangTidyChecker> configure(clang::CompilerInstance& instance,
|
||||
checker->context.setCurrentFile(file_name);
|
||||
checker->context.setSelfContainedDiags(true);
|
||||
checker->checks = factories.createChecksForLanguage(&checker->context);
|
||||
LOGGING_INFO("Tidy configure checks: {}", checker->checks.size());
|
||||
LOG_INFO("Tidy configure checks: {}", checker->checks.size());
|
||||
clang::Preprocessor* pp = &instance.getPreprocessor();
|
||||
for(const auto& check: checker->checks) {
|
||||
check->registerPPCallbacks(instance.getSourceManager(), pp, pp);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "Compiler/Toolchain.h"
|
||||
#include "Compiler/Command.h"
|
||||
#include "Support/FileSystem.h"
|
||||
#include "Support/Logging.h"
|
||||
#include "llvm/ADT/ScopeExit.h"
|
||||
@@ -6,96 +7,184 @@
|
||||
#include "llvm/Support/Program.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "clang/Driver/Driver.h"
|
||||
#include "clang/Driver/Compilation.h"
|
||||
#include "clang/Driver/Tool.h"
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
#include "llvm/TargetParser/Host.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
#include <unistd.h>
|
||||
extern char** environ;
|
||||
|
||||
static llvm::ArrayRef<llvm::StringRef> envs() {
|
||||
static std::vector<std::string> storage;
|
||||
static auto refs = [] {
|
||||
std::vector<llvm::StringRef> refs;
|
||||
if(environ) {
|
||||
for(char** env = environ; *env != nullptr; ++env) {
|
||||
llvm::StringRef s(*env);
|
||||
if(!s.starts_with("LANG=")) {
|
||||
storage.emplace_back(*env);
|
||||
}
|
||||
}
|
||||
|
||||
storage.emplace_back("LANG=C");
|
||||
}
|
||||
|
||||
/// Note that store the reference os strings in the vector
|
||||
/// is not safe when vector grows capacity. But we store it
|
||||
/// after all insertion are completed. It's safe here.
|
||||
for(const auto& s: storage) {
|
||||
refs.emplace_back(s);
|
||||
}
|
||||
|
||||
return refs;
|
||||
}();
|
||||
return refs;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
llvm::StringRef null_dev = "NUL";
|
||||
#else
|
||||
llvm::StringRef null_dev = "/dev/null";
|
||||
#endif
|
||||
|
||||
namespace clice::toolchain {
|
||||
|
||||
namespace opt = llvm::opt;
|
||||
namespace driver = clang::driver;
|
||||
namespace {
|
||||
|
||||
/// Checks if dash-dash (`--`) parsing is enabled. If enabled, all arguments
|
||||
/// after a standalone `--` are treated as positional arguments (e.g., input files).
|
||||
bool enable_dash_dash_parsing(const opt::OptTable& table);
|
||||
std::optional<std::string> execute_command(llvm::ArrayRef<const char*> arguments,
|
||||
bool capture_stdout = false) {
|
||||
LOG_INFO("Execute command: {}", print_argv(arguments));
|
||||
|
||||
/// Checks if grouped short options are enabled. If enabled, a short option group
|
||||
/// like `-ab` is parsed as separate options `-a` and `-b`.
|
||||
bool enable_grouped_short_options(const opt::OptTable& table);
|
||||
|
||||
/// Get the specific toolchain of given target, we mainly use it to get msvc toolchain.
|
||||
const driver::ToolChain& get_toolchain(driver::Driver& driver,
|
||||
const opt::ArgList& Args,
|
||||
const llvm::Triple& Target);
|
||||
|
||||
template <auto MP1, auto MP2, auto MP3>
|
||||
struct Thief {
|
||||
friend bool enable_dash_dash_parsing(const opt::OptTable& table) {
|
||||
return table.*MP1;
|
||||
llvm::SmallString<64> path;
|
||||
if(auto e = fs::createTemporaryFile("query-toolchain", "clice", path)) {
|
||||
LOG_ERROR_RET(std::nullopt, "Fail to create temporary file: {}", e);
|
||||
}
|
||||
|
||||
friend bool enable_grouped_short_options(const opt::OptTable& table) {
|
||||
return table.*MP2;
|
||||
auto _ = llvm::make_scope_exit([&path]() {
|
||||
if(auto e = fs::remove(path)) {
|
||||
LOG_ERROR("Fail to remove temporary file: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
#ifdef _WIN32
|
||||
/// If the env is `std::nullopt`, `ExecuteAndWait` will inherit env from parent process,
|
||||
/// which is very important for msvc and clang on windows. Thay depend on the environment
|
||||
/// variables to find correct standard library path.
|
||||
constexpr auto env = std::nullopt;
|
||||
#else
|
||||
/// For linux, we should append or modify the "LANG=C" to the env, this is important
|
||||
/// for gcc with locality. Otherwise, it will output non-ASCII char. We also want
|
||||
/// to inherit the environment variables like windows.
|
||||
auto env = envs();
|
||||
#endif
|
||||
|
||||
std::optional<llvm::StringRef> redirects[3] = {
|
||||
{null_dev}, // stdin
|
||||
{capture_stdout ? path.str() : null_dev}, // stdout
|
||||
{capture_stdout ? null_dev : path.str()}, // stderr
|
||||
};
|
||||
|
||||
llvm::SmallVector<llvm::StringRef> argv(arguments.begin(), arguments.end());
|
||||
|
||||
std::string message;
|
||||
if(int rc = llvm::sys::ExecuteAndWait(arguments[0],
|
||||
argv,
|
||||
env,
|
||||
redirects,
|
||||
/*SecondsToWait=*/0,
|
||||
/*MemoryLimit=*/0,
|
||||
&message)) {
|
||||
/// FIXME: handle error when rc is positive.
|
||||
LOG_ERROR_RET(std::nullopt,
|
||||
"Fail to execute {}, return code is {}, because: {}",
|
||||
arguments[0],
|
||||
rc,
|
||||
message);
|
||||
}
|
||||
|
||||
friend const driver::ToolChain& get_toolchain(driver::Driver& driver,
|
||||
const opt::ArgList& args,
|
||||
const llvm::Triple& target) {
|
||||
return (driver.*MP3)(args, target);
|
||||
auto file = llvm::MemoryBuffer::getFile(path);
|
||||
if(!file) {
|
||||
LOG_ERROR_RET(std::nullopt, "Fail to read redirect file: {}", file.getError());
|
||||
}
|
||||
};
|
||||
|
||||
template struct Thief<&opt::OptTable::DashDashParsing,
|
||||
&opt::OptTable::GroupedShortOptions,
|
||||
&driver::Driver::getToolChain>;
|
||||
|
||||
Toolchain query_toolchain(llvm::ArrayRef<const char*> arguments) {
|
||||
llvm::StringRef driver = arguments[0];
|
||||
|
||||
/// judge tool chain kind ...
|
||||
|
||||
return {};
|
||||
return file->get()->getBuffer().str();
|
||||
}
|
||||
|
||||
using ErrorKind = toolchain::QueryDriverError::ErrorKind;
|
||||
bool query_driver(
|
||||
llvm::ArrayRef<const char*> arguments,
|
||||
llvm::function_ref<void(const char* driver, llvm::ArrayRef<const char*> cc1_args)> callback) {
|
||||
/// FIXME: collect diagnostic here ...
|
||||
clang::DiagnosticOptions options;
|
||||
clang::DiagnosticsEngine engine(new clang::DiagnosticIDs(),
|
||||
options,
|
||||
new clang::IgnoringDiagConsumer());
|
||||
|
||||
auto unexpected(ErrorKind kind, std::string message) {
|
||||
return std::unexpected<toolchain::QueryDriverError>({kind, std::move(message)});
|
||||
};
|
||||
llvm::SmallVector<const char*, 256> list;
|
||||
list.emplace_back(arguments.consume_front());
|
||||
list.emplace_back("-fsyntax-only");
|
||||
list.append(arguments.begin(), arguments.end());
|
||||
arguments = list;
|
||||
|
||||
enum class CompilerFamily {
|
||||
Unknown,
|
||||
GCC, // Covers gcc, g++, cc, c++, and versioned/arch variants
|
||||
Clang, // Covers clang, clang++, and versioned variants (excluding clang-cl)
|
||||
MSVC, // Covers cl
|
||||
ClangCL, // Covers clang-cl explicitly
|
||||
NVCC, // Covers nvcc
|
||||
Intel, // Covers icc, icpc, icx, dpcpp
|
||||
Zig, // Covers zig cc / zig c++ (assumed GCC/Clang compatible for query)
|
||||
};
|
||||
/// Note that clang use the `ClangExecutable` to determine the driver mode when
|
||||
/// --driver-mode is not found in the arguments. and `TargetTriple` is used when
|
||||
/// non --target argument is found in the arguments list. See
|
||||
/// `clang::driver::BuildCompilation`. We use default arguments because we will
|
||||
/// inject related commands before querying.
|
||||
clang::driver::Driver driver(/*ClangExecutable=*/arguments[0],
|
||||
/*TargetTriple=*/llvm::sys::getDefaultTargetTriple(),
|
||||
/*Diags=*/engine);
|
||||
driver.setCheckInputsExist(false);
|
||||
driver.setProbePrecompiled(false);
|
||||
|
||||
CompilerFamily driver_family(llvm::StringRef driver) {
|
||||
auto driver_name = llvm::sys::path::filename(driver);
|
||||
driver_name.consume_back(".exe");
|
||||
if(driver_name == "cl") {
|
||||
return CompilerFamily::MSVC;
|
||||
} else if(driver_name == "nvcc") {
|
||||
return CompilerFamily::NVCC;
|
||||
} else if(driver_name.contains("clang-cl")) {
|
||||
return CompilerFamily::ClangCL;
|
||||
} else if(driver_name.contains("clang")) {
|
||||
return CompilerFamily::Clang;
|
||||
} else if(driver_name == "cc" || driver_name == "c++" || driver_name.contains("gcc") ||
|
||||
driver_name.contains("g++")) {
|
||||
return CompilerFamily::GCC;
|
||||
} else if(driver_name.contains("icpc") || driver_name.contains("icc") ||
|
||||
driver_name.contains("dpcpp") || driver_name.contains("icx")) {
|
||||
return CompilerFamily::Intel;
|
||||
} else if(driver_name.contains("zig")) {
|
||||
return CompilerFamily::Zig;
|
||||
std::unique_ptr<clang::driver::Compilation> compilation(driver.BuildCompilation(arguments));
|
||||
if(!compilation) {
|
||||
LOG_ERROR_RET(false, "Fail to query driver");
|
||||
}
|
||||
return CompilerFamily::Unknown;
|
||||
|
||||
// We expect to get back exactly one command job, if we didn't something
|
||||
// failed. Offload compilation is an exception as it creates multiple jobs. If
|
||||
// that's the case, we proceed with the first job. If caller needs a
|
||||
// particular job, it should be controlled via options (e.g.
|
||||
// --cuda-{host|device}-only for CUDA) passed to the driver.
|
||||
const clang::driver::JobList& jobs = compilation->getJobs();
|
||||
bool offload_compilation = false;
|
||||
if(jobs.size() > 1) {
|
||||
for(auto& action: compilation->getActions()) {
|
||||
// On MacOSX real actions may end up being wrapped in BindArchAction
|
||||
if(llvm::isa<clang::driver::BindArchAction>(action)) {
|
||||
action = *action->input_begin();
|
||||
}
|
||||
|
||||
if(llvm::isa<clang::driver::OffloadAction>(action)) {
|
||||
offload_compilation = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto cmd = llvm::find_if(jobs, [](const clang::driver::Command& cmd) {
|
||||
return cmd.getCreator().getName() == llvm::StringRef("clang");
|
||||
});
|
||||
if(cmd == jobs.end()) {
|
||||
LOG_ERROR_RET(false, "Fail to query driver, clang job was not found!");
|
||||
}
|
||||
|
||||
callback(arguments[0], cmd->getArguments());
|
||||
return true;
|
||||
}
|
||||
|
||||
auto parse_query_result(llvm::StringRef content, QueryResult& info)
|
||||
-> std::expected<void, QueryDriverError> {
|
||||
struct QueryResult {
|
||||
llvm::StringRef target;
|
||||
std::vector<llvm::StringRef> includes;
|
||||
};
|
||||
|
||||
/// TODO: use this to print the output of -v.
|
||||
void parse_version_result(llvm::StringRef content, QueryResult& info) {
|
||||
const char* TS = "Target: ";
|
||||
const char* SIS = "#include <...> search starts here:";
|
||||
const char* SIE = "End of search list.";
|
||||
@@ -134,18 +223,69 @@ auto parse_query_result(llvm::StringRef content, QueryResult& info)
|
||||
}
|
||||
|
||||
if(!found_start_marker) {
|
||||
return unexpected(ErrorKind::InvalidOutputFormat, "Start marker not found...");
|
||||
LOG_ERROR("Failed to parse version output: missing include search start marker");
|
||||
return;
|
||||
}
|
||||
|
||||
if(in_includes_block) {
|
||||
return unexpected(ErrorKind::InvalidOutputFormat, "End marker not found...");
|
||||
LOG_ERROR("Failed to parse version output: unclosed include search block");
|
||||
return;
|
||||
}
|
||||
|
||||
return std::expected<void, QueryDriverError>();
|
||||
}
|
||||
|
||||
auto query_driver(llvm::StringRef driver) -> std::expected<QueryResult, QueryDriverError> {
|
||||
llvm::SmallString<128> path;
|
||||
} // namespace
|
||||
|
||||
CompilerFamily driver_family(llvm::StringRef driver) {
|
||||
auto try_get = [](llvm::StringRef name) {
|
||||
if(name == "cl") {
|
||||
return CompilerFamily::MSVC;
|
||||
} else if(name == "nvcc") {
|
||||
return CompilerFamily::NVCC;
|
||||
} else if(name.ends_with("clang") || name.ends_with("clang++")) {
|
||||
return CompilerFamily::Clang;
|
||||
} else if(name.ends_with("clang-cl")) {
|
||||
return CompilerFamily::ClangCL;
|
||||
} else if(name.ends_with("cc") || name.ends_with("c++") || name.ends_with("gcc") ||
|
||||
name.ends_with("g++")) {
|
||||
return CompilerFamily::GCC;
|
||||
} else if(name.contains("icpc") || name.contains("icc") || name.contains("dpcpp") ||
|
||||
name.contains("icx")) {
|
||||
return CompilerFamily::Intel;
|
||||
} else if(name.ends_with("zig")) {
|
||||
return CompilerFamily::Zig;
|
||||
}
|
||||
return CompilerFamily::Unknown;
|
||||
};
|
||||
|
||||
auto driver_name = llvm::sys::path::filename(driver);
|
||||
auto family = try_get(driver_name);
|
||||
if(family != CompilerFamily::Unknown) {
|
||||
return family;
|
||||
}
|
||||
|
||||
// Stripping the executable suffix: clang++.exe -> clang++
|
||||
driver_name.consume_back(".exe");
|
||||
family = try_get(driver_name);
|
||||
if(family != CompilerFamily::Unknown) {
|
||||
return family;
|
||||
}
|
||||
|
||||
// Stripping any trailing version number: clang++3.5 -> clang++
|
||||
driver_name = driver_name.rtrim("0123456789.-");
|
||||
family = try_get(driver_name);
|
||||
if(family != CompilerFamily::Unknown) {
|
||||
return family;
|
||||
}
|
||||
|
||||
/// Stripping trailing -component. clang++-tot -> clang++
|
||||
driver_name = driver_name.slice(0, driver_name.rfind('-'));
|
||||
family = try_get(driver_name);
|
||||
return family;
|
||||
}
|
||||
|
||||
std::vector<const char*> query_toolchain(const QueryParams& params) {
|
||||
auto arguments = params.arguments;
|
||||
llvm::StringRef driver = arguments[0];
|
||||
|
||||
/// Note: The name used to invoke the compiler driver affects its behavior.
|
||||
/// For example, `/usr/bin/clang++` is often a symbolic link to
|
||||
@@ -153,130 +293,198 @@ auto query_driver(llvm::StringRef driver) -> std::expected<QueryResult, QueryDri
|
||||
/// and links C++ libraries by default, while invoking as `clang` defaults to C mode.
|
||||
/// Therefore, never use `realpath` on the initial `driver` name, as that
|
||||
/// would lose the context needed for the driver to behave correctly (and break caching).
|
||||
if(!llvm::sys::path::is_absolute(driver)) {
|
||||
llvm::SmallString<128> path;
|
||||
if(!path::is_absolute(driver)) {
|
||||
/// If the path is not absolute path like g++, find it in the env vars.
|
||||
auto program = llvm::sys::findProgramByName(driver);
|
||||
if(!program) {
|
||||
return unexpected(ErrorKind::NotFoundInPATH, program.getError().message());
|
||||
LOG_ERROR_RET({}, "Fail to query driver, cannot find the driver: {}", driver);
|
||||
}
|
||||
path = *program;
|
||||
driver = path;
|
||||
driver = path.c_str();
|
||||
}
|
||||
|
||||
/// Check whether we can execute the driver.
|
||||
if(!llvm::sys::fs::exists(driver) || !llvm::sys::fs::can_execute(driver)) {
|
||||
/// FIXME: Add whitelisting, blacklisting (do not trust workspace executables),
|
||||
/// and toolchain integrity checks.
|
||||
return unexpected(ErrorKind::NotFoundInPATH, "");
|
||||
if(!fs::exists(driver) || !fs::can_execute(driver)) {
|
||||
LOG_ERROR_RET({}, "Fail to query driver, driver: {} is not existent or executable", driver);
|
||||
}
|
||||
|
||||
auto params_copy = params;
|
||||
llvm::SmallVector<const char*, 256> modified_arguments;
|
||||
|
||||
/// Remove driver
|
||||
arguments.consume_front();
|
||||
modified_arguments.emplace_back(driver.data());
|
||||
|
||||
/// Remove input file
|
||||
auto ext = path::extension(params.file);
|
||||
ext.consume_front(".");
|
||||
|
||||
modified_arguments.append(arguments.begin(), arguments.end());
|
||||
|
||||
/// Create a file with same suffix of input file, because the input file may
|
||||
/// not exist in the disk.
|
||||
llvm::SmallString<64> src_path;
|
||||
if(auto e = fs::createTemporaryFile("query-toolchain", ext, src_path)) {
|
||||
LOG_ERROR_RET({}, "Fail to create temporary file: {}", e);
|
||||
}
|
||||
auto _ = llvm::make_scope_exit([&src_path]() {
|
||||
if(auto e = fs::remove(src_path)) {
|
||||
LOG_ERROR("Fail to remove temporary file: {}", e);
|
||||
}
|
||||
});
|
||||
modified_arguments.emplace_back(src_path.c_str());
|
||||
arguments = modified_arguments;
|
||||
params_copy.arguments = arguments;
|
||||
|
||||
auto family = driver_family(driver);
|
||||
|
||||
/// FIXME: Handle nvcc and intel compiler.
|
||||
if(family == CompilerFamily::NVCC || family == CompilerFamily::Intel ||
|
||||
family == CompilerFamily::Zig || family == CompilerFamily::Unknown) [[unlikely]] {
|
||||
/// FIXME: nvcc and intel compilers need further exploration.
|
||||
/// zig is easy to handle, just use `zig cc` or `zig c++`, then
|
||||
/// it will behave like clang.
|
||||
return unexpected(ErrorKind::NotImplemented, "");
|
||||
}
|
||||
|
||||
/// Query the compiler for includes information.
|
||||
if(family == CompilerFamily::GCC || family == CompilerFamily::Clang) {
|
||||
llvm::SmallString<128> output_path;
|
||||
if(auto error =
|
||||
llvm::sys::fs::createTemporaryFile("system-includes", "clice", output_path)) {
|
||||
return unexpected(ErrorKind::FailToCreateTempFile, error.message());
|
||||
switch(family) {
|
||||
case CompilerFamily::GCC: {
|
||||
return query_gcc_toolchain(params_copy);
|
||||
}
|
||||
|
||||
// If we fail to get the driver infomation, keep the output file for user to debug.
|
||||
bool keep_output_file = true;
|
||||
auto clean_up = llvm::make_scope_exit([&output_path, &keep_output_file]() {
|
||||
if(keep_output_file) {
|
||||
LOGGING_WARN("Query driver failed, output file:{}", output_path);
|
||||
return;
|
||||
}
|
||||
|
||||
if(auto errc = llvm::sys::fs::remove(output_path)) {
|
||||
LOGGING_WARN("Fail to remove temporary file: {}", errc.message());
|
||||
}
|
||||
});
|
||||
|
||||
/// FIXME: Is it possible that the output is not in stderr?
|
||||
std::optional<llvm::StringRef> redirects[3] = {
|
||||
{""},
|
||||
{""},
|
||||
{output_path.str()},
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
/// If the env is `std::nullopt`, `ExecuteAndWait` will inherit env from parent process,
|
||||
/// which is very important for msvc and clang on windows. Thay depend on the environment
|
||||
/// variables to find correct standard library path.
|
||||
constexpr auto env = std::nullopt;
|
||||
|
||||
llvm::SmallVector<llvm::StringRef, 6> argv = {driver, "-E", "-v", "-xc++", "NUL"};
|
||||
#else
|
||||
/// FIXME: We should find a better way to convert "LANG=C", this is important
|
||||
/// for gcc with locality. Otherwise, it will output non-ASCII char. We also
|
||||
/// want to inherit the environment variables like windows.
|
||||
llvm::SmallVector<llvm::StringRef> env = {"LANG=C"};
|
||||
llvm::SmallVector<llvm::StringRef> argv = {driver, "-E", "-v", "-xc++", "/dev/null"};
|
||||
#endif
|
||||
|
||||
std::string message;
|
||||
if(int RC = llvm::sys::ExecuteAndWait(driver,
|
||||
argv,
|
||||
env,
|
||||
redirects,
|
||||
/*SecondsToWait=*/0,
|
||||
/*MemoryLimit=*/0,
|
||||
&message)) {
|
||||
return unexpected(ErrorKind::InvokeDriverFail, std::move(message));
|
||||
case CompilerFamily::Clang:
|
||||
case CompilerFamily::Zig: {
|
||||
return query_clang_toolchain(params_copy);
|
||||
}
|
||||
case CompilerFamily::MSVC:
|
||||
case CompilerFamily::ClangCL: {
|
||||
return query_msvc_toolchain(params_copy);
|
||||
}
|
||||
|
||||
auto file = llvm::MemoryBuffer::getFile(output_path);
|
||||
if(!file) {
|
||||
return unexpected(ErrorKind::OutputFileNotReadable, file.getError().message());
|
||||
}
|
||||
case CompilerFamily::NVCC:
|
||||
case CompilerFamily::Intel:
|
||||
case CompilerFamily::Unknown: {
|
||||
/// TODO: nvcc and intel compilers need further exploration.
|
||||
LOG_ERROR("Fail to query driver, unknown supported driver kind: {}, driver is {}",
|
||||
refl::enum_name(family),
|
||||
driver);
|
||||
|
||||
QueryResult info;
|
||||
if(auto r = parse_query_result(file.get()->getBuffer(), info)) {
|
||||
keep_output_file = false;
|
||||
return info;
|
||||
} else {
|
||||
return std::unexpected(r.error());
|
||||
std::vector<const char*> result;
|
||||
query_driver(params_copy.arguments,
|
||||
[&](const char* driver, llvm::ArrayRef<const char*> cc1_args) {
|
||||
result.emplace_back(params.callback(driver));
|
||||
for(auto arg: cc1_args) {
|
||||
result.emplace_back(params.callback(arg));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// For msvc and clang-cl, we don't need to query driver. Just use clang
|
||||
/// tool chain to find the built includes.
|
||||
if(family == CompilerFamily::MSVC || family == CompilerFamily::ClangCL) {
|
||||
/// FIXME: target information? e.g. arm cross compilation.
|
||||
llvm::StringRef target = "x86_64-pc-windows-msvc";
|
||||
|
||||
/// An workaround to use clang's toolchain to find vsinstall information
|
||||
/// and related includes.
|
||||
clang::DiagnosticOptions options;
|
||||
thread_local clang::DiagnosticsEngine engine(new clang::DiagnosticIDs(), options);
|
||||
clang::driver::Driver driver("", target, engine);
|
||||
llvm::SmallVector<const char*> args = {"", "-xc++", "NUL"};
|
||||
llvm::opt::InputArgList list(args.begin(), args.end());
|
||||
auto& toolchain = get_toolchain(driver, list, llvm::Triple(target));
|
||||
|
||||
/// FIXME: specify specific version of vs?
|
||||
llvm::opt::ArgStringList includes;
|
||||
toolchain.AddClangSystemIncludeArgs(list, includes);
|
||||
|
||||
QueryResult info;
|
||||
info.target = target;
|
||||
for(auto& include: includes) {
|
||||
info.includes.emplace_back(include);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
std::unreachable();
|
||||
}
|
||||
|
||||
std::vector<const char*> query_gcc_toolchain(const QueryParams& params) {
|
||||
auto arguments = params.arguments;
|
||||
llvm::SmallVector<const char*, 256> query_arguments;
|
||||
|
||||
llvm::SmallString<64> target;
|
||||
llvm::SmallString<64> install_path;
|
||||
|
||||
query_arguments = {arguments[0], "-dumpmachine"};
|
||||
if(auto content = execute_command(query_arguments, true)) {
|
||||
target = llvm::StringRef(*content).trim();
|
||||
}
|
||||
|
||||
query_arguments = {arguments[0], "-print-search-dirs"};
|
||||
if(auto content = execute_command(query_arguments, true)) {
|
||||
llvm::SmallVector<llvm::StringRef, 5> lines;
|
||||
llvm::StringRef(*content).split(lines, '\n', -1, /*KeepEmpty=*/false);
|
||||
for(auto line: lines) {
|
||||
line = line.trim();
|
||||
if(line.consume_front_insensitive("install:")) {
|
||||
install_path = line.trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
target = std::format("--target={}", target);
|
||||
install_path = std::format("--gcc-install-dir={}", install_path);
|
||||
|
||||
query_arguments.clear();
|
||||
query_arguments.emplace_back(arguments.consume_front());
|
||||
query_arguments.emplace_back(target.c_str());
|
||||
query_arguments.emplace_back(install_path.c_str());
|
||||
query_arguments.append(arguments.begin(), arguments.end());
|
||||
|
||||
std::vector<const char*> result;
|
||||
query_driver(query_arguments, [&](const char* driver, llvm::ArrayRef<const char*> cc1_args) {
|
||||
result.emplace_back(params.callback(driver));
|
||||
for(auto arg: cc1_args) {
|
||||
result.emplace_back(params.callback(arg));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<const char*> query_clang_toolchain(const QueryParams& params) {
|
||||
auto arguments = params.arguments;
|
||||
llvm::SmallVector<const char*, 256> query_arguments;
|
||||
|
||||
if(driver_family(arguments[0]) == CompilerFamily::Zig) {
|
||||
/// zig cc or zig c++ consumes two arguments.
|
||||
query_arguments.emplace_back(arguments.consume_front());
|
||||
query_arguments.emplace_back(arguments.consume_front());
|
||||
} else {
|
||||
query_arguments.emplace_back(arguments.consume_front());
|
||||
}
|
||||
|
||||
query_arguments.emplace_back("-###");
|
||||
query_arguments.emplace_back("-fsyntax-only");
|
||||
query_arguments.append(arguments.begin(), arguments.end());
|
||||
|
||||
std::vector<const char*> result;
|
||||
if(auto content = execute_command(query_arguments, false)) {
|
||||
llvm::SmallVector<llvm::StringRef> lines;
|
||||
llvm::StringRef(*content).split(lines, '\n', -1, /*KeepEmpty=*/false);
|
||||
|
||||
for(llvm::StringRef line: lines) {
|
||||
line = line.trim();
|
||||
|
||||
if(line.empty() || line.front() != '"') {
|
||||
continue;
|
||||
}
|
||||
|
||||
llvm::SmallVector<const char*, 256> args;
|
||||
llvm::BumpPtrAllocator allocator;
|
||||
llvm::StringSaver saver(allocator);
|
||||
llvm::cl::TokenizeGNUCommandLine(line, saver, args);
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
if(args.size() < 2 || args[1] != "-cc1"sv) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for(auto arg: args) {
|
||||
if(arg == "-###"sv) {
|
||||
continue;
|
||||
}
|
||||
result.emplace_back(params.callback(arg));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<const char*> query_msvc_toolchain(const QueryParams& params) {
|
||||
auto arguments = params.arguments;
|
||||
llvm::SmallVector<const char*, 256> query_arguments;
|
||||
|
||||
query_arguments.emplace_back(arguments.consume_front());
|
||||
/// When clang in cl mode, the target will be set to windows-msvc automatically.
|
||||
/// We don't need to add extra flag.
|
||||
query_arguments.emplace_back("--driver-mode=cl");
|
||||
query_arguments.append(arguments.begin(), arguments.end());
|
||||
|
||||
std::vector<const char*> result;
|
||||
query_driver(query_arguments, [&](const char* driver, llvm::ArrayRef<const char*> cc) {
|
||||
result.emplace_back(params.callback(driver));
|
||||
for(auto c: cc) {
|
||||
result.emplace_back(params.callback(c));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<const char*> query_nvcc_toolchain(const QueryParams& params);
|
||||
|
||||
} // namespace clice::toolchain
|
||||
|
||||
@@ -32,9 +32,9 @@ json::Value diagnostics(PositionEncodingKind kind, PathMapping mapping, Compilat
|
||||
if(level == DiagnosticLevel::Note || level == DiagnosticLevel::Remark) {
|
||||
/// FIXME: figure out why it may be invalid.
|
||||
if(fid.isInvalid()) {
|
||||
LOGGING_INFO("code: {}, message: {}",
|
||||
raw_diagnostic.id.diagnostic_code(),
|
||||
raw_diagnostic.message);
|
||||
LOG_INFO("code: {}, message: {}",
|
||||
raw_diagnostic.id.diagnostic_code(),
|
||||
raw_diagnostic.message);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ std::vector<proto::TextEdit> document_format(llvm::StringRef file,
|
||||
range ? tooling::Range(range->begin, range->length()) : tooling::Range(0, content.size());
|
||||
auto replacements = format(file, content, selection);
|
||||
if(!replacements) {
|
||||
LOGGING_INFO("Fail to format for {}\n{}", file, replacements.error());
|
||||
LOG_INFO("Fail to format for {}\n{}", file, replacements.error());
|
||||
return edits;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,14 +12,14 @@ void Server::load_cache_info() {
|
||||
auto path = path::join(config.project.cache_dir, "cache.json");
|
||||
auto file = llvm::MemoryBuffer::getFile(path);
|
||||
if(!file) {
|
||||
LOGGING_WARN("Fail to load cache info, because: {}", file.getError());
|
||||
LOG_WARN("Fail to load cache info, because: {}", file.getError());
|
||||
return;
|
||||
}
|
||||
|
||||
llvm::StringRef content = file.get()->getBuffer();
|
||||
auto json = json::parse(content);
|
||||
if(!json) {
|
||||
LOGGING_WARN("Fail to load cache info, invalid json: {}", json.takeError());
|
||||
LOG_WARN("Fail to load cache info, invalid json: {}", json.takeError());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ void Server::load_cache_info() {
|
||||
|
||||
auto version = object->getString("version");
|
||||
if(!version) {
|
||||
LOGGING_INFO("Fail to load cache info, the cache info is outdated");
|
||||
LOG_INFO("Fail to load cache info, the cache info is outdated");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ void Server::load_cache_info() {
|
||||
}
|
||||
}
|
||||
|
||||
LOGGING_INFO("Load cache info successfully");
|
||||
LOG_INFO("Load cache info successfully");
|
||||
}
|
||||
|
||||
void Server::save_cache_info() {
|
||||
@@ -105,20 +105,20 @@ void Server::save_cache_info() {
|
||||
|
||||
llvm::SmallString<128> temp_path;
|
||||
if(auto error = llvm::sys::fs::createTemporaryFile("cache", "json", temp_path)) {
|
||||
LOGGING_WARN("Fail to create temporary file for cache info: {}", error.message());
|
||||
LOG_WARN("Fail to create temporary file for cache info: {}", error.message());
|
||||
return;
|
||||
}
|
||||
|
||||
auto clean_up = llvm::make_scope_exit([&temp_path]() {
|
||||
if(auto errc = llvm::sys::fs::remove(temp_path)) {
|
||||
LOGGING_WARN("Fail to remove temporary file: {}", errc.message());
|
||||
LOG_WARN("Fail to remove temporary file: {}", errc.message());
|
||||
}
|
||||
});
|
||||
|
||||
std::error_code EC;
|
||||
llvm::raw_fd_ostream os(temp_path, EC, llvm::sys::fs::OF_None);
|
||||
if(EC) {
|
||||
LOGGING_WARN("Fail to open temporary file for writing: {}", EC.message());
|
||||
LOG_WARN("Fail to open temporary file for writing: {}", EC.message());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -127,24 +127,26 @@ void Server::save_cache_info() {
|
||||
os.close();
|
||||
|
||||
if(os.has_error()) {
|
||||
LOGGING_WARN("Fail to write cache info to temporary file");
|
||||
LOG_WARN("Fail to write cache info to temporary file");
|
||||
return;
|
||||
}
|
||||
|
||||
if(auto error = llvm::sys::fs::rename(temp_path, final_path)) {
|
||||
LOGGING_WARN("Fail to rename temporary file to final cache file: {}", error.message());
|
||||
LOG_WARN("Fail to rename temporary file to final cache file: {}", error.message());
|
||||
return;
|
||||
}
|
||||
|
||||
clean_up.release();
|
||||
|
||||
LOGGING_INFO("Save cache info successfully");
|
||||
LOG_INFO("Save cache info successfully");
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool
|
||||
check_pch_update(llvm::StringRef content, std::uint32_t bound, LookupInfo& info, PCHInfo& pch) {
|
||||
bool check_pch_update(llvm::StringRef content,
|
||||
std::uint32_t bound,
|
||||
CompilationContext& info,
|
||||
PCHInfo& pch) {
|
||||
if(content.substr(0, bound) != pch.preamble) {
|
||||
return true;
|
||||
}
|
||||
@@ -168,7 +170,7 @@ bool
|
||||
}
|
||||
|
||||
/// The actual PCH build task.
|
||||
async::Task<bool> build_pch_task(LookupInfo& info,
|
||||
async::Task<bool> build_pch_task(CompilationContext& info,
|
||||
std::string cache_dir,
|
||||
std::shared_ptr<OpenFile> open_file,
|
||||
std::string path,
|
||||
@@ -178,7 +180,7 @@ async::Task<bool> build_pch_task(LookupInfo& info,
|
||||
if(!fs::exists(cache_dir)) {
|
||||
auto error = fs::create_directories(cache_dir);
|
||||
if(error) {
|
||||
LOGGING_WARN("Fail to create directory for PCH building: {}", cache_dir);
|
||||
LOG_WARN("Fail to create directory for PCH building: {}", cache_dir);
|
||||
co_return false;
|
||||
}
|
||||
}
|
||||
@@ -189,6 +191,7 @@ async::Task<bool> build_pch_task(LookupInfo& info,
|
||||
CompilationParams params;
|
||||
params.kind = CompilationUnit::Preamble;
|
||||
params.output_file = path::join(cache_dir, path::filename(path) + ".pch");
|
||||
params.arguments_from_database = true;
|
||||
params.arguments = std::move(info.arguments);
|
||||
params.diagnostics = diagnostics;
|
||||
params.add_remapped_file(path, content, bound);
|
||||
@@ -199,7 +202,7 @@ async::Task<bool> build_pch_task(LookupInfo& info,
|
||||
command += argument;
|
||||
}
|
||||
|
||||
LOGGING_INFO("Start building PCH for {}, command: [{}]", path, command);
|
||||
LOG_INFO("Start building PCH for {}, command: [{}]", path, command);
|
||||
command.clear();
|
||||
|
||||
PCHInfo pch;
|
||||
@@ -220,14 +223,14 @@ async::Task<bool> build_pch_task(LookupInfo& info,
|
||||
});
|
||||
|
||||
if(!success) {
|
||||
LOGGING_WARN("Building PCH fails for {}, Because: {}", path, message);
|
||||
LOG_WARN("Building PCH fails for {}, Because: {}", path, message);
|
||||
for(auto& diagnostic: *diagnostics) {
|
||||
LOGGING_WARN("{}", diagnostic.message);
|
||||
LOG_WARN("{}", diagnostic.message);
|
||||
}
|
||||
co_return false;
|
||||
}
|
||||
|
||||
LOGGING_INFO("Building PCH successfully for {}", path);
|
||||
LOG_INFO("Building PCH successfully for {}", path);
|
||||
|
||||
/// Update the built PCH info.
|
||||
open_file->pch = std::move(pch);
|
||||
@@ -245,7 +248,7 @@ async::Task<bool> build_pch_task(LookupInfo& info,
|
||||
async::Task<bool> Server::build_pch(std::string file, std::string content) {
|
||||
CommandOptions options;
|
||||
options.resource_dir = true;
|
||||
options.query_driver = true;
|
||||
options.query_toolchain = true;
|
||||
auto info = database.lookup(file, options);
|
||||
|
||||
auto bound = compute_preamble_bound(content);
|
||||
@@ -254,7 +257,7 @@ async::Task<bool> Server::build_pch(std::string file, std::string content) {
|
||||
/// Check update ...
|
||||
if(open_file->pch && !check_pch_update(content, bound, info, *open_file->pch)) {
|
||||
/// If not need update, return directly.
|
||||
LOGGING_INFO("PCH is already up-to-date for {}", file);
|
||||
LOG_INFO("PCH is already up-to-date for {}", file);
|
||||
co_return true;
|
||||
}
|
||||
|
||||
@@ -263,12 +266,12 @@ async::Task<bool> Server::build_pch(std::string file, std::string content) {
|
||||
if(!task.empty()) {
|
||||
if(task.finished()) {
|
||||
task.release().destroy();
|
||||
LOGGING_INFO("Release old pch task!");
|
||||
LOG_INFO("Release old pch task!");
|
||||
} else {
|
||||
task.cancel();
|
||||
task.dispose();
|
||||
}
|
||||
LOGGING_INFO("Cancel old PCH building task!");
|
||||
LOG_INFO("Cancel old PCH building task!");
|
||||
}
|
||||
|
||||
/// Schedule the new building task.
|
||||
@@ -304,15 +307,16 @@ async::Task<> Server::build_ast(std::string path, std::string content) {
|
||||
|
||||
auto pch = file->pch;
|
||||
if(!pch) {
|
||||
LOGGING_FATAL("Expected PCH built at this point");
|
||||
LOG_FATAL("Expected PCH built at this point");
|
||||
}
|
||||
|
||||
CommandOptions options;
|
||||
options.resource_dir = true;
|
||||
options.query_driver = true;
|
||||
options.query_toolchain = true;
|
||||
|
||||
CompilationParams params;
|
||||
params.kind = CompilationUnit::Content;
|
||||
params.arguments_from_database = true;
|
||||
params.arguments = database.lookup(path, options).arguments;
|
||||
params.add_remapped_file(path, content);
|
||||
params.pch = {pch->path, pch->preamble.size()};
|
||||
@@ -324,9 +328,9 @@ async::Task<> Server::build_ast(std::string path, std::string content) {
|
||||
auto ast = co_await async::submit([&] { return compile(params); });
|
||||
if(!ast) {
|
||||
/// FIXME: Fails needs cancel waiting tasks.
|
||||
LOGGING_WARN("Building AST fails for {}, Beacuse: {}", path, ast.error());
|
||||
LOG_ERROR("Building AST fails for {}, Beacuse: {}", path, ast.error());
|
||||
for(auto& diagnostic: *file->diagnostics) {
|
||||
LOGGING_WARN("{}", diagnostic.message);
|
||||
LOG_ERROR("{}", diagnostic.message);
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
@@ -349,7 +353,7 @@ async::Task<> Server::build_ast(std::string path, std::string content) {
|
||||
/// Dispose the task so that it will destroyed when task complete.
|
||||
file->ast_build_task.dispose();
|
||||
|
||||
LOGGING_INFO("Building AST successfully for {}", path);
|
||||
LOG_INFO("Building AST successfully for {}", path);
|
||||
}
|
||||
|
||||
async::Task<std::shared_ptr<OpenFile>> Server::add_document(std::string path, std::string content) {
|
||||
@@ -363,12 +367,12 @@ async::Task<std::shared_ptr<OpenFile>> Server::add_document(std::string path, st
|
||||
if(!task.empty()) {
|
||||
if(task.finished()) {
|
||||
task.release().destroy();
|
||||
LOGGING_INFO("Release old AST building Task!");
|
||||
LOG_INFO("Release old AST building Task!");
|
||||
} else {
|
||||
task.cancel();
|
||||
task.dispose();
|
||||
}
|
||||
LOGGING_INFO("Cancel old AST building Task!");
|
||||
LOG_INFO("Cancel old AST building Task!");
|
||||
}
|
||||
|
||||
/// Create and schedule a new task.
|
||||
|
||||
@@ -31,6 +31,7 @@ auto Server::on_completion(proto::CompletionParams params) -> Result {
|
||||
/// Set compilation params ... .
|
||||
CompilationParams params;
|
||||
params.kind = CompilationUnit::Completion;
|
||||
params.arguments_from_database = true;
|
||||
params.arguments = database.lookup(path).arguments;
|
||||
params.add_remapped_file(path, content);
|
||||
params.pch = {pch->path, pch->preamble.size()};
|
||||
@@ -88,6 +89,7 @@ async::Task<json::Value> Server::on_signature_help(proto::SignatureHelpParams pa
|
||||
/// Set compilation params ... .
|
||||
CompilationParams params;
|
||||
params.kind = CompilationUnit::Completion;
|
||||
params.arguments_from_database = true;
|
||||
params.arguments = database.lookup(path, options).arguments;
|
||||
params.add_remapped_file(path, content);
|
||||
params.pch = {pch->path, pch->preamble.size()};
|
||||
|
||||
@@ -10,12 +10,13 @@ namespace clice {
|
||||
async::Task<> Indexer::index(llvm::StringRef path) {
|
||||
CompilationParams params;
|
||||
params.kind = CompilationUnit::Indexing;
|
||||
params.arguments_from_database = true;
|
||||
params.arguments = database.lookup(path).arguments;
|
||||
|
||||
auto path_id = project_index.path_pool.path_id(path);
|
||||
auto& merged_index = get_index(path_id);
|
||||
if(!merged_index.need_update(project_index.path_pool.paths)) {
|
||||
LOGGING_INFO("Check update for {}, not need to update", path);
|
||||
LOG_INFO("Check update for {}, not need to update", path);
|
||||
co_return;
|
||||
}
|
||||
|
||||
@@ -25,7 +26,7 @@ async::Task<> Indexer::index(llvm::StringRef path) {
|
||||
auto tu_index = co_await async::submit([&]() -> std::optional<index::TUIndex> {
|
||||
auto unit = compile(params);
|
||||
if(!unit) {
|
||||
LOGGING_INFO("Fail to index for {}, because: {}", path, unit.error());
|
||||
LOG_INFO("Fail to index for {}, because: {}", path, unit.error());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -55,7 +56,7 @@ async::Task<> Indexer::index(llvm::StringRef path) {
|
||||
std::move(tu_index->graph.locations),
|
||||
tu_index->main_file_index);
|
||||
|
||||
LOGGING_INFO("Successfully index {}", path);
|
||||
LOG_INFO("Successfully index {}", path);
|
||||
}
|
||||
|
||||
async::Task<> Indexer::schedule_next() {
|
||||
@@ -107,9 +108,9 @@ void Indexer::load_from_disk() {
|
||||
if(auto content = fs::read(output_path); content && !content->empty()) {
|
||||
/// FIXME: from should return a expected ...
|
||||
project_index = index::ProjectIndex::from(content->data());
|
||||
LOGGING_INFO("Load project index form {} successfully", output_path);
|
||||
LOG_INFO("Load project index form {} successfully", output_path);
|
||||
} else {
|
||||
LOGGING_INFO("Fail to load project index form {}", output_path);
|
||||
LOG_INFO("Fail to load project index form {}", output_path);
|
||||
}
|
||||
|
||||
/// FIXME: check indices update ....
|
||||
@@ -117,9 +118,7 @@ void Indexer::load_from_disk() {
|
||||
|
||||
void Indexer::save_to_disk() {
|
||||
if(auto err = fs::create_directories(config.project.index_dir)) {
|
||||
LOGGING_WARN("Fail to create index output dir: {}, because: {}",
|
||||
config.project.index_dir,
|
||||
err);
|
||||
LOG_WARN("Fail to create index output dir: {}, because: {}", config.project.index_dir, err);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -139,7 +138,7 @@ void Indexer::save_to_disk() {
|
||||
std::error_code err;
|
||||
llvm::raw_fd_ostream os(output_path, err, fs::CreationDisposition::CD_CreateAlways);
|
||||
if(err) {
|
||||
LOGGING_INFO("Fail to create output index file: {}, because: {}", output_path, err);
|
||||
LOG_INFO("Fail to create output index file: {}, because: {}", output_path, err);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -147,7 +146,7 @@ void Indexer::save_to_disk() {
|
||||
|
||||
auto opath_id = project_index.path_pool.path_id(output_path);
|
||||
project_index.indices.try_emplace(path_id, opath_id);
|
||||
LOGGING_INFO("Successfully save index for {} to {}", path, output_path);
|
||||
LOG_INFO("Successfully save index for {} to {}", path, output_path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,12 +155,12 @@ void Indexer::save_to_disk() {
|
||||
std::error_code err;
|
||||
llvm::raw_fd_ostream os(output_path, err, fs::CreationDisposition::CD_CreateAlways);
|
||||
if(err) {
|
||||
LOGGING_INFO("Fail to create output index file: {}, because: {}", output_path, err);
|
||||
LOG_INFO("Fail to create output index file: {}, because: {}", output_path, err);
|
||||
return;
|
||||
}
|
||||
|
||||
project_index.serialize(os);
|
||||
LOGGING_INFO("Successfully save project index to {}", output_path);
|
||||
LOG_INFO("Successfully save project index to {}", output_path);
|
||||
}
|
||||
|
||||
auto Indexer::lookup(llvm::StringRef path, std::uint32_t offset, RelationKind kind) -> Result {
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
namespace clice {
|
||||
|
||||
async::Task<json::Value> Server::on_initialize(proto::InitializeParams params) {
|
||||
LOGGING_INFO("Initialize from client: {}, version: {}",
|
||||
params.clientInfo.name,
|
||||
params.clientInfo.version);
|
||||
LOG_INFO("Initialize from client: {}, version: {}",
|
||||
params.clientInfo.name,
|
||||
params.clientInfo.version);
|
||||
|
||||
/// FIXME: adjust position encoding.
|
||||
kind = PositionEncodingKind::UTF16;
|
||||
@@ -17,15 +17,15 @@ async::Task<json::Value> Server::on_initialize(proto::InitializeParams params) {
|
||||
return *params.rootUri;
|
||||
}
|
||||
|
||||
LOGGING_FATAL("The client should provide one workspace folder or rootUri at least!");
|
||||
LOG_FATAL("The client should provide one workspace folder or rootUri at least!");
|
||||
})());
|
||||
|
||||
/// Initialize configuration.
|
||||
if(auto result = config.parse(workspace)) {
|
||||
LOGGING_INFO("Config initialized successfully: {0:4}", json::serialize(config));
|
||||
LOG_INFO("Config initialized successfully: {0:4}", json::serialize(config));
|
||||
} else {
|
||||
LOGGING_WARN("Fail to load config, because: {0}", result.error());
|
||||
LOGGING_INFO("Use default config: {0:4}", json::serialize(config));
|
||||
LOG_WARN("Fail to load config, because: {0}", result.error());
|
||||
LOG_INFO("Use default config: {0:4}", json::serialize(config));
|
||||
}
|
||||
|
||||
if(!config.project.logging_dir.empty()) {
|
||||
@@ -36,7 +36,9 @@ async::Task<json::Value> Server::on_initialize(proto::InitializeParams params) {
|
||||
opening_files.set_capability(config.project.max_active_file);
|
||||
|
||||
/// Load compile commands.json
|
||||
database.load_compile_database(config.project.compile_commands_dirs, workspace);
|
||||
for(auto& dir: config.project.compile_commands_dirs) {
|
||||
database.load_compile_database(path::join(dir, "compile_commands.json"));
|
||||
}
|
||||
|
||||
/// Load cache info.
|
||||
load_cache_info();
|
||||
|
||||
@@ -124,7 +124,7 @@ Server::Server() : indexer(database, config, kind) {
|
||||
async::Task<> Server::on_receive(json::Value value) {
|
||||
auto object = value.getAsObject();
|
||||
if(!object) [[unlikely]] {
|
||||
LOGGING_FATAL("Invalid LSP message, not an object: {}", value);
|
||||
LOG_FATAL("Invalid LSP message, not an object: {}", value);
|
||||
}
|
||||
|
||||
/// If the json object has an `id`, it's a request,
|
||||
@@ -135,7 +135,7 @@ async::Task<> Server::on_receive(json::Value value) {
|
||||
if(auto result = object->getString("method")) {
|
||||
method = *result;
|
||||
} else [[unlikely]] {
|
||||
LOGGING_WARN("Invalid LSP message, method not found: {}", value);
|
||||
LOG_WARN("Invalid LSP message, method not found: {}", value);
|
||||
if(id) {
|
||||
co_await response(std::move(*id),
|
||||
proto::ErrorCodes::InvalidRequest,
|
||||
@@ -152,7 +152,7 @@ async::Task<> Server::on_receive(json::Value value) {
|
||||
/// Handle request and notification separately.
|
||||
auto it = callbacks.find(method);
|
||||
if(it == callbacks.end()) {
|
||||
LOGGING_INFO("Ignore unhandled method: {}", method);
|
||||
LOG_INFO("Ignore unhandled method: {}", method);
|
||||
co_return;
|
||||
}
|
||||
|
||||
@@ -160,24 +160,24 @@ async::Task<> Server::on_receive(json::Value value) {
|
||||
auto current_id = client_request_id++;
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
|
||||
LOGGING_INFO("<-- Handling request: {}({})", method, current_id);
|
||||
LOG_INFO("<-- Handling request: {}({})", method, current_id);
|
||||
auto result = co_await it->second(*this, std::move(params));
|
||||
co_await response(std::move(*id), std::move(result));
|
||||
|
||||
auto end_time = std::chrono::steady_clock::now();
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
|
||||
LOGGING_INFO("--> Handled request: {}({}) {}ms", method, current_id, duration.count());
|
||||
LOG_INFO("--> Handled request: {}({}) {}ms", method, current_id, duration.count());
|
||||
} else {
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
LOGGING_INFO("<-- Handling notification: {}", method);
|
||||
LOG_INFO("<-- Handling notification: {}", method);
|
||||
|
||||
auto result = co_await it->second(*this, std::move(params));
|
||||
|
||||
auto end_time = std::chrono::steady_clock::now();
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
|
||||
LOGGING_INFO("--> Handled notification: {} {}ms", method, duration.count());
|
||||
LOG_INFO("--> Handled notification: {} {}ms", method, duration.count());
|
||||
}
|
||||
|
||||
co_return;
|
||||
|
||||
@@ -213,14 +213,16 @@ std::optional<SourceRange> toHalfOpenFileRange(const SourceManager& SM,
|
||||
} // namespace
|
||||
|
||||
suite<"SelectionTree"> selection = [] {
|
||||
auto select_right = [](llvm::StringRef code, auto&& callback) {
|
||||
auto select_right = [](llvm::StringRef code,
|
||||
auto&& callback,
|
||||
std::source_location location = std::source_location::current()) {
|
||||
Tester tester;
|
||||
tester.add_main("main.cpp", code);
|
||||
expect(that % tester.compile());
|
||||
fatal / expect(that % tester.compile(), location);
|
||||
/// expect(that % tester.unit->diagnostics().empty());
|
||||
|
||||
auto points = tester.nameless_points();
|
||||
expect(that % points.size() >= 1);
|
||||
expect(that % points.size() >= 1, location);
|
||||
|
||||
LocalSourceRange selected_range;
|
||||
selected_range.begin = points[0];
|
||||
@@ -229,28 +231,33 @@ suite<"SelectionTree"> selection = [] {
|
||||
callback(tester, tree);
|
||||
};
|
||||
|
||||
auto expect_select = [&](llvm::StringRef code, const char* kind) {
|
||||
select_right(code, [&](Tester& tester, SelectionTree& tree) {
|
||||
auto node = tree.common_ancestor();
|
||||
if(!kind) {
|
||||
expect(that % !node);
|
||||
} else {
|
||||
expect(that % node);
|
||||
auto range2 = toHalfOpenFileRange(tester.unit->context().getSourceManager(),
|
||||
tester.unit->lang_options(),
|
||||
node->source_range());
|
||||
LocalSourceRange range = {
|
||||
tester.unit->file_offset(range2->getBegin()),
|
||||
tester.unit->file_offset(range2->getEnd()),
|
||||
};
|
||||
auto expect_select = [&](llvm::StringRef code,
|
||||
const char* kind,
|
||||
std::source_location location = std::source_location::current()) {
|
||||
select_right(
|
||||
code,
|
||||
[&](Tester& tester, SelectionTree& tree) {
|
||||
auto node = tree.common_ancestor();
|
||||
if(!kind) {
|
||||
expect(that % !node, location);
|
||||
} else {
|
||||
expect(that % node, location);
|
||||
auto range2 = toHalfOpenFileRange(tester.unit->context().getSourceManager(),
|
||||
tester.unit->lang_options(),
|
||||
node->source_range());
|
||||
LocalSourceRange range = {
|
||||
tester.unit->file_offset(range2->getBegin()),
|
||||
tester.unit->file_offset(range2->getEnd()),
|
||||
};
|
||||
|
||||
/// llvm::outs() << tree << "\n";
|
||||
/// tree.print(llvm::outs(), *node, 2);
|
||||
/// llvm::outs() << tree << "\n";
|
||||
/// tree.print(llvm::outs(), *node, 2);
|
||||
|
||||
expect(that % node->kind() == llvm::StringRef(kind));
|
||||
expect(that % range == tester.range());
|
||||
}
|
||||
});
|
||||
expect(that % node->kind() == llvm::StringRef(kind), location);
|
||||
expect(that % range == tester.range(), location);
|
||||
}
|
||||
},
|
||||
location);
|
||||
};
|
||||
|
||||
test("Expressions") = [&] {
|
||||
|
||||
@@ -93,7 +93,7 @@ suite<"Command"> command = [] {
|
||||
auto expect_strip = [](llvm::StringRef argv, llvm::StringRef result) {
|
||||
CompilationDatabase database;
|
||||
llvm::StringRef file = "main.cpp";
|
||||
database.update_command("fake/", file, argv);
|
||||
database.add_command("fake/", file, argv);
|
||||
|
||||
CommandOptions options;
|
||||
options.suppress_logging = true;
|
||||
@@ -101,10 +101,10 @@ suite<"Command"> command = [] {
|
||||
};
|
||||
|
||||
/// Filter -c, -o and input file.
|
||||
expect_strip("g++ main.cc", "g++ main.cpp");
|
||||
expect_strip("g++ main.cpp", "g++ main.cpp");
|
||||
expect_strip("clang++ -c main.cpp", "clang++ main.cpp");
|
||||
expect_strip("clang++ -o main.o main.cpp", "clang++ main.cpp");
|
||||
expect_strip("clang++ -c -o main.o main.cc", "clang++ main.cpp");
|
||||
expect_strip("clang++ -c -o main.o main.cpp", "clang++ main.cpp");
|
||||
expect_strip("cl.exe /c /Fomain.cpp.o main.cpp", "cl.exe main.cpp");
|
||||
|
||||
/// Filter PCH related.
|
||||
@@ -126,8 +126,8 @@ suite<"Command"> command = [] {
|
||||
using namespace std::literals;
|
||||
|
||||
CompilationDatabase database;
|
||||
database.update_command("fake", "test.cpp", "clang++ -std=c++23 test.cpp"sv);
|
||||
database.update_command("fake", "test2.cpp", "clang++ -std=c++23 test2.cpp"sv);
|
||||
database.add_command("fake", "test.cpp", "clang++ -std=c++23 test.cpp"sv);
|
||||
database.add_command("fake", "test2.cpp", "clang++ -std=c++23 test2.cpp"sv);
|
||||
|
||||
CommandOptions options;
|
||||
options.suppress_logging = true;
|
||||
@@ -157,7 +157,7 @@ suite<"Command"> command = [] {
|
||||
};
|
||||
|
||||
CompilationDatabase database;
|
||||
database.update_command("/fake", "main.cpp", args);
|
||||
database.add_command("/fake", "main.cpp", args);
|
||||
|
||||
CommandOptions options;
|
||||
|
||||
@@ -198,56 +198,24 @@ suite<"Command"> command = [] {
|
||||
skip / test("Module") = [] {
|
||||
/// TODO:
|
||||
CompilationDatabase database;
|
||||
database.update_command("/fake",
|
||||
"main.cpp",
|
||||
llvm::StringRef("clang++ @test.txt -std= main.cpp"));
|
||||
auto info = database.lookup("main.cpp", {.query_driver = false});
|
||||
};
|
||||
|
||||
skip_unless(CIEnvironment) / test("QueryDriver") = [] {
|
||||
CompilationDatabase database;
|
||||
auto info = database.query_driver("clang++");
|
||||
|
||||
fatal / expect(info);
|
||||
expect(!info->target.empty());
|
||||
expect(!info->system_includes.empty());
|
||||
|
||||
CompilationParams params;
|
||||
params.kind = CompilationUnit::Indexing;
|
||||
params.arguments = {
|
||||
"clang++",
|
||||
"-nostdlibinc",
|
||||
"--target",
|
||||
info->target.data(),
|
||||
};
|
||||
for(auto& include: info->system_includes) {
|
||||
params.arguments.push_back("-I");
|
||||
params.arguments.push_back(include);
|
||||
}
|
||||
params.arguments.push_back("main.cpp");
|
||||
|
||||
llvm::StringRef hello_world = R"(
|
||||
#include <iostream>
|
||||
int main() {
|
||||
std::cout << "Hello world!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
)";
|
||||
params.add_remapped_file("main.cpp", hello_world);
|
||||
expect(compile(params));
|
||||
database.add_command("/fake",
|
||||
"main.cpp",
|
||||
llvm::StringRef("clang++ @test.txt -std= main.cpp"));
|
||||
auto info = database.lookup("main.cpp", {.query_toolchain = false});
|
||||
};
|
||||
|
||||
test("ResourceDir") = [] {
|
||||
CompilationDatabase database;
|
||||
using namespace std::literals;
|
||||
database.update_command("/fake", "main.cpp", "clang++ -std=c++23 test.cpp"sv);
|
||||
database.add_command("/fake", "main.cpp", "clang++ -std=c++23 test.cpp"sv);
|
||||
auto arguments = database.lookup("main.cpp", {.resource_dir = true}).arguments;
|
||||
|
||||
fatal / expect(eq(arguments.size(), 4));
|
||||
fatal / expect(eq(arguments.size(), 5));
|
||||
expect(eq(arguments[0], "clang++"sv));
|
||||
expect(eq(arguments[1], "-std=c++23"sv));
|
||||
expect(eq(arguments[2], std::format("-resource-dir={}", fs::resource_dir)));
|
||||
expect(eq(arguments[3], "main.cpp"sv));
|
||||
expect(eq(arguments[2], "-resource-dir"sv));
|
||||
expect(eq(arguments[3], fs::resource_dir));
|
||||
expect(eq(arguments[4], "main.cpp"sv));
|
||||
};
|
||||
|
||||
auto expect_load = [](llvm::StringRef content,
|
||||
@@ -273,75 +241,87 @@ suite<"Command"> command = [] {
|
||||
};
|
||||
|
||||
/// TODO: add windows path testcase
|
||||
skip_unless(Linux || MacOS) / test("LoadAbsoluteUnixStyle") = [expect_load] {
|
||||
constexpr const char* cmake = R"([
|
||||
{
|
||||
"directory": "/home/developer/clice/build",
|
||||
"command": "/usr/bin/c++ -I/home/developer/clice/include -I/home/developer/clice/build/_deps/libuv-src/include -isystem /home/developer/clice/build/_deps/tomlplusplus-src/include -std=gnu++23 -fno-rtti -fno-exceptions -Wno-deprecated-declarations -Wno-undefined-inline -O3 -o CMakeFiles/clice-core.dir/src/Driver/clice.cpp.o -c /home/developer/clice/src/Driver/clice.cpp",
|
||||
"file": "/home/developer/clice/src/Driver/clice.cpp",
|
||||
"output": "CMakeFiles/clice-core.dir/src/Driver/clice.cpp.o"
|
||||
}
|
||||
])";
|
||||
// skip_unless(Linux || MacOS) / test("LoadAbsoluteUnixStyle") = [expect_load] {
|
||||
// constexpr const char* cmake = R"([
|
||||
// {
|
||||
// "directory": "/home/developer/clice/build",
|
||||
// "command": "/usr/bin/c++ -I/home/developer/clice/include
|
||||
// -I/home/developer/clice/build/_deps/libuv-src/include -isystem
|
||||
// /home/developer/clice/build/_deps/tomlplusplus-src/include -std=gnu++23 -fno-rtti
|
||||
// -fno-exceptions -Wno-deprecated-declarations -Wno-undefined-inline -O3 -o
|
||||
// CMakeFiles/clice-core.dir/src/Driver/clice.cpp.o -c
|
||||
// /home/developer/clice/src/Driver/clice.cpp", "file":
|
||||
// "/home/developer/clice/src/Driver/clice.cpp", "output":
|
||||
// "CMakeFiles/clice-core.dir/src/Driver/clice.cpp.o"
|
||||
// }
|
||||
// ])";
|
||||
//
|
||||
// expect_load(cmake,
|
||||
// "/home/developer/clice",
|
||||
// "/home/developer/clice/src/Driver/clice.cpp",
|
||||
// "/home/developer/clice/build",
|
||||
// {
|
||||
// "/usr/bin/c++",
|
||||
// "-I",
|
||||
// "/home/developer/clice/include",
|
||||
// "-I",
|
||||
// "/home/developer/clice/build/_deps/libuv-src/include",
|
||||
// "-isystem",
|
||||
// "/home/developer/clice/build/_deps/tomlplusplus-src/include",
|
||||
// "-std=gnu++23",
|
||||
// "-fno-rtti",
|
||||
// "-fno-exceptions",
|
||||
// "-Wno-deprecated-declarations",
|
||||
// "-Wno-undefined-inline",
|
||||
// "-O3",
|
||||
// "/home/developer/clice/src/Driver/clice.cpp",
|
||||
// });
|
||||
// };
|
||||
|
||||
expect_load(cmake,
|
||||
"/home/developer/clice",
|
||||
"/home/developer/clice/src/Driver/clice.cpp",
|
||||
"/home/developer/clice/build",
|
||||
{
|
||||
"/usr/bin/c++",
|
||||
"-I",
|
||||
"/home/developer/clice/include",
|
||||
"-I",
|
||||
"/home/developer/clice/build/_deps/libuv-src/include",
|
||||
"-isystem",
|
||||
"/home/developer/clice/build/_deps/tomlplusplus-src/include",
|
||||
"-std=gnu++23",
|
||||
"-fno-rtti",
|
||||
"-fno-exceptions",
|
||||
"-Wno-deprecated-declarations",
|
||||
"-Wno-undefined-inline",
|
||||
"-O3",
|
||||
"/home/developer/clice/src/Driver/clice.cpp",
|
||||
});
|
||||
};
|
||||
|
||||
skip_unless(Linux || MacOS) / test("LoadRelativeUnixStyle") = [expect_load] {
|
||||
constexpr const char* xmake = R"([
|
||||
{
|
||||
"directory": "/home/developer/clice",
|
||||
"arguments": ["/usr/bin/clang", "-c", "-Qunused-arguments", "-m64", "-g", "-O0", "-std=c++23", "-Iinclude", "-I/home/developer/clice/include", "-fno-exceptions", "-fno-cxx-exceptions", "-isystem", "/home/developer/.xmake/packages/l/libuv/v1.51.0/3ca1562e6c5d485f9ccafec8e0c50b6f/include", "-isystem", "/home/developer/.xmake/packages/t/toml++/v3.4.0/bde7344d843e41928b1d325fe55450e0/include", "-fsanitize=address", "-fno-rtti", "-o", "build/.objs/clice/linux/x86_64/debug/src/Driver/clice.cc.o", "src/Driver/clice.cc"],
|
||||
"file": "src/Driver/clice.cc"
|
||||
}
|
||||
])";
|
||||
|
||||
expect_load(
|
||||
xmake,
|
||||
"/home/developer/clice",
|
||||
"/home/developer/clice/src/Driver/clice.cc",
|
||||
"/home/developer/clice",
|
||||
{
|
||||
"/usr/bin/clang",
|
||||
"-Qunused-arguments",
|
||||
"-m64",
|
||||
"-g",
|
||||
"-O0",
|
||||
"-std=c++23",
|
||||
// parameter "-Iinclude" in CDB, should be convert to absolute path
|
||||
"-I",
|
||||
"/home/developer/clice/include",
|
||||
"-I",
|
||||
"/home/developer/clice/include",
|
||||
"-fno-exceptions",
|
||||
"-fno-cxx-exceptions",
|
||||
"-isystem",
|
||||
"/home/developer/.xmake/packages/l/libuv/v1.51.0/3ca1562e6c5d485f9ccafec8e0c50b6f/include",
|
||||
"-isystem",
|
||||
"/home/developer/.xmake/packages/t/toml++/v3.4.0/bde7344d843e41928b1d325fe55450e0/include",
|
||||
"-fsanitize=address",
|
||||
"-fno-rtti",
|
||||
"/home/developer/clice/src/Driver/clice.cc",
|
||||
});
|
||||
};
|
||||
// skip_unless(Linux || MacOS) / test("LoadRelativeUnixStyle") = [expect_load] {
|
||||
// constexpr const char* xmake = R"([
|
||||
// {
|
||||
// "directory": "/home/developer/clice",
|
||||
// "arguments": ["/usr/bin/clang", "-c", "-Qunused-arguments", "-m64", "-g", "-O0",
|
||||
// "-std=c++23", "-Iinclude", "-I/home/developer/clice/include", "-fno-exceptions",
|
||||
// "-fno-cxx-exceptions", "-isystem",
|
||||
// "/home/developer/.xmake/packages/l/libuv/v1.51.0/3ca1562e6c5d485f9ccafec8e0c50b6f/include",
|
||||
// "-isystem",
|
||||
// "/home/developer/.xmake/packages/t/toml++/v3.4.0/bde7344d843e41928b1d325fe55450e0/include",
|
||||
// "-fsanitize=address", "-fno-rtti", "-o",
|
||||
// "build/.objs/clice/linux/x86_64/debug/src/Driver/clice.cc.o", "src/Driver/clice.cc"],
|
||||
// "file": "src/Driver/clice.cc"
|
||||
// }
|
||||
// ])";
|
||||
//
|
||||
// expect_load(
|
||||
// xmake,
|
||||
// "/home/developer/clice",
|
||||
// "/home/developer/clice/src/Driver/clice.cc",
|
||||
// "/home/developer/clice",
|
||||
// {
|
||||
// "/usr/bin/clang",
|
||||
// "-Qunused-arguments",
|
||||
// "-m64",
|
||||
// "-g",
|
||||
// "-O0",
|
||||
// "-std=c++23",
|
||||
// // parameter "-Iinclude" in CDB, should be convert to absolute path
|
||||
// "-I",
|
||||
// "/home/developer/clice/include",
|
||||
// "-I",
|
||||
// "/home/developer/clice/include",
|
||||
// "-fno-exceptions",
|
||||
// "-fno-cxx-exceptions",
|
||||
// "-isystem",
|
||||
// "/home/developer/.xmake/packages/l/libuv/v1.51.0/3ca1562e6c5d485f9ccafec8e0c50b6f/include",
|
||||
// "-isystem",
|
||||
// "/home/developer/.xmake/packages/t/toml++/v3.4.0/bde7344d843e41928b1d325fe55450e0/include",
|
||||
// "-fsanitize=address",
|
||||
// "-fno-rtti",
|
||||
// "/home/developer/clice/src/Driver/clice.cc",
|
||||
// });
|
||||
//};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
126
tests/unit/Compiler/Toolchain.cpp
Normal file
126
tests/unit/Compiler/Toolchain.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
#include "Compiler/Compilation.h"
|
||||
#include "Test/Test.h"
|
||||
#include "Compiler/Toolchain.h"
|
||||
#include "llvm/Support/Allocator.h"
|
||||
#include "llvm/Support/StringSaver.h"
|
||||
#include "clang/Driver/Driver.h"
|
||||
#include "Support/Logging.h"
|
||||
|
||||
namespace clice::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
suite<"Toolchain"> suite = [] {
|
||||
auto expect_family = [](llvm::StringRef name,
|
||||
toolchain::CompilerFamily family,
|
||||
std::source_location location = std::source_location::current()) {
|
||||
expect(eq(refl::enum_name(toolchain::driver_family(name)), refl::enum_name((family))),
|
||||
location);
|
||||
};
|
||||
|
||||
test("Family") = [&] {
|
||||
using enum toolchain::CompilerFamily;
|
||||
|
||||
expect_family("gcc", GCC);
|
||||
expect_family("g++", GCC);
|
||||
expect_family("x86_64-linux-gnu-g++-14", GCC);
|
||||
expect_family("arm-none-eabi-gcc", GCC);
|
||||
|
||||
expect_family("clang", Clang);
|
||||
expect_family("clang++", Clang);
|
||||
expect_family("clang.exe", Clang);
|
||||
expect_family("clang++.exe", Clang);
|
||||
expect_family("clang-20", Clang);
|
||||
expect_family("clang-20.exe", Clang);
|
||||
expect_family("clang-cl", ClangCL);
|
||||
expect_family("clang-cl-20", ClangCL);
|
||||
expect_family("clang-cl-20.exe", ClangCL);
|
||||
|
||||
expect_family("cl.exe", MSVC);
|
||||
|
||||
expect_family("zig", Zig);
|
||||
expect_family("zig.exe", Zig);
|
||||
};
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
skip_unless(CIEnvironment && (Windows || Linux)) / test("GCC") = [] {
|
||||
auto file = fs::createTemporaryFile("clice", "cpp");
|
||||
if(!file) {
|
||||
LOG_ERROR_RET(void(), "{}", file.error());
|
||||
}
|
||||
|
||||
llvm::BumpPtrAllocator a;
|
||||
llvm::StringSaver s(a);
|
||||
auto arguments = toolchain::query_toolchain({
|
||||
.arguments = {"g++",
|
||||
"-std=c++23", "-resource-dir",
|
||||
fs::resource_dir.c_str(),
|
||||
"-xc++", file->c_str()},
|
||||
.callback = [&](const char* str) { return s.save(str).data(); }
|
||||
});
|
||||
|
||||
expect(arguments.size() > 2);
|
||||
expect(eq(arguments[1], "-cc1"sv));
|
||||
|
||||
CompilationParams params;
|
||||
params.arguments_from_database = true;
|
||||
params.arguments = arguments;
|
||||
params.add_remapped_file(file->c_str(), R"(
|
||||
#include <print>
|
||||
int main() {
|
||||
std::println("Hello world!");
|
||||
return 0;
|
||||
}
|
||||
)");
|
||||
|
||||
auto unit = compile(params);
|
||||
expect(unit && unit->diagnostics().empty());
|
||||
};
|
||||
|
||||
skip_unless(CIEnvironment) / test("MSVC") = [] {
|
||||
|
||||
};
|
||||
|
||||
skip_unless(CIEnvironment) / test("Clang") = [] {
|
||||
auto file = fs::createTemporaryFile("clice", "cpp");
|
||||
if(!file) {
|
||||
LOG_ERROR_RET(void(), "{}", file.error());
|
||||
}
|
||||
|
||||
llvm::BumpPtrAllocator a;
|
||||
llvm::StringSaver s(a);
|
||||
auto arguments = toolchain::query_toolchain({
|
||||
.arguments = {"clang++",
|
||||
"-std=c++23", "-resource-dir",
|
||||
fs::resource_dir.c_str(),
|
||||
"-xc++", file->c_str()},
|
||||
.callback = [&](const char* str) { return s.save(str).data(); }
|
||||
});
|
||||
|
||||
expect(arguments.size() > 2);
|
||||
expect(eq(arguments[1], "-cc1"sv));
|
||||
|
||||
CompilationParams params;
|
||||
params.arguments_from_database = true;
|
||||
params.arguments = arguments;
|
||||
params.add_remapped_file(file->c_str(), R"(
|
||||
#include <print>
|
||||
int main() {
|
||||
std::println("Hello world!");
|
||||
return 0;
|
||||
}
|
||||
)");
|
||||
|
||||
auto unit = compile(params);
|
||||
expect(unit && unit->diagnostics().empty());
|
||||
};
|
||||
|
||||
skip_unless(CIEnvironment) / test("Zig") = [] {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace clice::testing
|
||||
@@ -36,7 +36,7 @@ struct GetUSRVisitor : public clang::RecursiveASTVisitor<GetUSRVisitor> {
|
||||
|
||||
if(offset.has_value() && USR.has_value()) {
|
||||
USRs[*offset] = USRInfo{*USR, *offset, decl};
|
||||
// LOGGING_INFO("USR: {} at {}:{}", USR->str(), pos->line, pos->character);
|
||||
// LOG_INFO("USR: {} at {}:{}", USR->str(), pos->line, pos->character);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -61,7 +61,7 @@ struct USRTester : public Tester {
|
||||
llvm::StringRef lookup(llvm::StringRef key) {
|
||||
auto iter = USRs.find((*this)["main.cpp", key]);
|
||||
if(iter == USRs.end()) {
|
||||
LOGGING_FATAL("USR not found for key: {}", key);
|
||||
LOG_FATAL("USR not found for key: {}", key);
|
||||
}
|
||||
return iter->second.USR;
|
||||
}
|
||||
|
||||
165
tests/unit/Test/Tester.cpp
Normal file
165
tests/unit/Test/Tester.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
#include "Test/Tester.h"
|
||||
|
||||
namespace clice::testing {
|
||||
|
||||
void Tester::prepare(llvm::StringRef standard) {
|
||||
auto command = std::format("clang++ {} {} -fms-extensions", standard, src_path);
|
||||
|
||||
database.add_command("fake", src_path, command);
|
||||
params.kind = CompilationUnit::Content;
|
||||
|
||||
CommandOptions options;
|
||||
options.resource_dir = true;
|
||||
options.query_toolchain = true;
|
||||
options.suppress_logging = true;
|
||||
|
||||
params.arguments_from_database = true;
|
||||
params.arguments = database.lookup(src_path, options).arguments;
|
||||
|
||||
for(auto& [file, source]: sources.all_files) {
|
||||
if(file == src_path) {
|
||||
params.add_remapped_file(file, source.content);
|
||||
} else {
|
||||
/// FIXME: This is a workaround.
|
||||
std::string path = path::is_absolute(file) ? file.str() : path::join(".", file);
|
||||
params.add_remapped_file(path, source.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Tester::compile(llvm::StringRef standard) {
|
||||
prepare(standard);
|
||||
|
||||
auto unit = clice::compile(params);
|
||||
if(!unit) {
|
||||
LOG_ERROR("{}", unit.error());
|
||||
for(auto& diag: *params.diagnostics) {
|
||||
LOG_ERROR("{}", diag.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this->unit.emplace(std::move(*unit));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Tester::compile_with_pch(llvm::StringRef standard) {
|
||||
params.diagnostics = std::make_shared<std::vector<Diagnostic>>();
|
||||
auto command = std::format("clang++ {} {} -fms-extensions", standard, src_path);
|
||||
|
||||
database.add_command("fake", src_path, command);
|
||||
params.kind = CompilationUnit::Preamble;
|
||||
|
||||
CommandOptions options;
|
||||
options.resource_dir = true;
|
||||
options.query_toolchain = true;
|
||||
options.suppress_logging = true;
|
||||
|
||||
params.arguments_from_database = true;
|
||||
params.arguments = database.lookup(src_path, options).arguments;
|
||||
|
||||
auto path = fs::createTemporaryFile("clice", "pch");
|
||||
if(!path) {
|
||||
llvm::outs() << path.error().message() << "\n";
|
||||
}
|
||||
|
||||
/// Build PCH
|
||||
params.output_file = *path;
|
||||
|
||||
for(auto& [file, source]: sources.all_files) {
|
||||
if(file == src_path) {
|
||||
auto bound = compute_preamble_bound(source.content);
|
||||
params.add_remapped_file(file, source.content, bound);
|
||||
} else {
|
||||
/// FIXME: This is a workaround.
|
||||
std::string path = path::is_absolute(file) ? file.str() : path::join(".", file);
|
||||
params.add_remapped_file(path, source.content);
|
||||
}
|
||||
}
|
||||
|
||||
PCHInfo info;
|
||||
{
|
||||
auto unit = clice::compile(params, info);
|
||||
if(!unit) {
|
||||
LOG_ERROR("{}", unit.error());
|
||||
for(auto& diag: *params.diagnostics) {
|
||||
LOG_ERROR("{}", diag.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Build AST
|
||||
params.output_file.clear();
|
||||
params.kind = CompilationUnit::Content;
|
||||
params.pch = {info.path, info.preamble.size()};
|
||||
for(auto& [file, source]: sources.all_files) {
|
||||
if(file == src_path) {
|
||||
params.add_remapped_file(file, source.content);
|
||||
} else {
|
||||
/// FIXME: This is a workaround.
|
||||
std::string path = path::is_absolute(file) ? file.str() : path::join(".", file);
|
||||
params.add_remapped_file(path, source.content);
|
||||
}
|
||||
}
|
||||
|
||||
auto unit = clice::compile(params);
|
||||
if(!unit) {
|
||||
LOG_ERROR("{}", unit.error());
|
||||
for(auto& diag: *params.diagnostics) {
|
||||
LOG_ERROR("{}", diag.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this->unit.emplace(std::move(*unit));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint32_t Tester::point(llvm::StringRef name, llvm::StringRef file) {
|
||||
if(file.empty()) {
|
||||
file = src_path;
|
||||
}
|
||||
|
||||
auto& offsets = sources.all_files[file].offsets;
|
||||
if(name.empty()) {
|
||||
assert(offsets.size() == 1);
|
||||
return offsets.begin()->second;
|
||||
} else {
|
||||
assert(offsets.contains(name));
|
||||
return offsets.lookup(name);
|
||||
}
|
||||
}
|
||||
|
||||
llvm::ArrayRef<std::uint32_t> Tester::nameless_points(llvm::StringRef file) {
|
||||
if(file.empty()) {
|
||||
file = src_path;
|
||||
}
|
||||
|
||||
return sources.all_files[file].nameless_offsets;
|
||||
}
|
||||
|
||||
LocalSourceRange Tester::range(llvm::StringRef name, llvm::StringRef file) {
|
||||
if(file.empty()) {
|
||||
file = src_path;
|
||||
}
|
||||
|
||||
auto& ranges = sources.all_files[file].ranges;
|
||||
if(name.empty()) {
|
||||
assert(ranges.size() == 1);
|
||||
return ranges.begin()->second;
|
||||
} else {
|
||||
assert(ranges.contains(name));
|
||||
return ranges.lookup(name);
|
||||
}
|
||||
}
|
||||
|
||||
void Tester::clear() {
|
||||
params = CompilationParams();
|
||||
database = CompilationDatabase();
|
||||
unit.reset();
|
||||
sources.all_files.clear();
|
||||
src_path.clear();
|
||||
}
|
||||
|
||||
} // namespace clice::testing
|
||||
Reference in New Issue
Block a user