214 lines
6.1 KiB
C++
214 lines
6.1 KiB
C++
#pragma once
|
|
|
|
#include <expected>
|
|
|
|
#include "Assert.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/FileSystem.h"
|
|
#include "llvm/Support/VirtualFileSystem.h"
|
|
#include "llvm/Support/MemoryBuffer.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
|
|
namespace clice {
|
|
|
|
namespace path {
|
|
|
|
using namespace llvm::sys::path;
|
|
|
|
template <typename... Args>
|
|
std::string join(Args&&... args) {
|
|
llvm::SmallString<128> path;
|
|
((path::append(path, std::forward<Args>(args))), ...);
|
|
return path.str().str();
|
|
}
|
|
|
|
/// Get the real path of the given file. The file must exist. If the file does not exist,
|
|
|
|
inline std::string real_path(llvm::StringRef file) {
|
|
llvm::SmallString<128> path;
|
|
auto error = llvm::sys::fs::real_path(file, path);
|
|
ASSERT(!error, "Failed to get real path of {0}, because {1}", file, error.message());
|
|
return path.str().str();
|
|
}
|
|
|
|
} // namespace path
|
|
|
|
namespace fs {
|
|
|
|
using namespace llvm::sys::fs;
|
|
|
|
inline std::string resource_dir = "";
|
|
|
|
inline std::expected<void, std::error_code> init_resource_dir(llvm::StringRef execute) {
|
|
llvm::SmallString<128> path;
|
|
path::append(path, path::parent_path(execute), "..");
|
|
path::append(path, "lib", "clang", "20");
|
|
if(auto error = real_path(path, path)) {
|
|
return std::unexpected(error);
|
|
}
|
|
resource_dir = path.str();
|
|
return std::expected<void, std::error_code>();
|
|
}
|
|
|
|
inline std::expected<std::string, std::error_code> createTemporaryFile(llvm::StringRef prefix,
|
|
llvm::StringRef suffix) {
|
|
llvm::SmallString<128> path;
|
|
auto error = llvm::sys::fs::createTemporaryFile(prefix, suffix, path);
|
|
if(error) {
|
|
return std::unexpected(error);
|
|
}
|
|
return path.str().str();
|
|
}
|
|
|
|
inline std::expected<void, std::error_code> write(llvm::StringRef path, llvm::StringRef content) {
|
|
std::error_code EC;
|
|
llvm::raw_fd_ostream os(path, EC, llvm::sys::fs::OF_None);
|
|
if(EC) {
|
|
return std::unexpected(EC);
|
|
}
|
|
os << content;
|
|
os.flush();
|
|
return std::expected<void, std::error_code>();
|
|
}
|
|
|
|
inline std::expected<std::string, std::error_code> read(llvm::StringRef path) {
|
|
auto buffer = llvm::MemoryBuffer::getFile(path);
|
|
if(!buffer) {
|
|
return std::unexpected(buffer.getError());
|
|
}
|
|
return buffer.get()->getBuffer().str();
|
|
}
|
|
|
|
inline std::string toURI(llvm::StringRef fspath) {
|
|
if(!path::is_absolute(fspath))
|
|
std::abort();
|
|
|
|
llvm::SmallString<128> path("file://");
|
|
#if defined(_WIN32)
|
|
path.append("/");
|
|
#endif
|
|
|
|
for(auto c: fspath) {
|
|
if(c == '\\') {
|
|
path.push_back('/');
|
|
} else if(std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '/') {
|
|
path.push_back(c);
|
|
} else {
|
|
path.push_back('%');
|
|
path.push_back(llvm::hexdigit(c >> 4));
|
|
path.push_back(llvm::hexdigit(c & 0xF));
|
|
}
|
|
}
|
|
|
|
return path.str().str();
|
|
}
|
|
|
|
inline std::string decodePercent(llvm::StringRef content) {
|
|
std::string result;
|
|
result.reserve(content.size());
|
|
|
|
for(auto iter = content.begin(), send = content.end(); iter != send; ++iter) {
|
|
auto c = *iter;
|
|
if(c == '%' && iter + 2 < send) {
|
|
auto m = *(iter + 1);
|
|
auto n = *(iter + 2);
|
|
if(llvm::isHexDigit(m) && llvm::isHexDigit(n)) {
|
|
result += llvm::hexFromNibbles(m, n);
|
|
iter += 2;
|
|
continue;
|
|
}
|
|
}
|
|
result += c;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
inline std::string toPath(llvm::StringRef uri) {
|
|
llvm::StringRef cloned = uri;
|
|
|
|
#if defined(_WIN32)
|
|
if(cloned.starts_with("file:///")) {
|
|
cloned = cloned.drop_front(8);
|
|
} else {
|
|
std::abort();
|
|
}
|
|
#elif defined(__unix__)
|
|
if(cloned.starts_with("file://")) {
|
|
cloned = cloned.drop_front(7);
|
|
} else {
|
|
std::abort();
|
|
}
|
|
#else
|
|
#error "Unsupported platform"
|
|
#endif
|
|
|
|
auto decoded = decodePercent(cloned);
|
|
|
|
llvm::SmallString<128> result;
|
|
if(auto err = fs::real_path(decoded, result)) {
|
|
print("Failed to get real path: {}, Input is {}\n", err.message(), decoded);
|
|
std::abort();
|
|
}
|
|
|
|
return result.str().str();
|
|
}
|
|
|
|
} // namespace fs
|
|
|
|
namespace vfs = llvm::vfs;
|
|
|
|
class ThreadSafeFS : public vfs::ProxyFileSystem {
|
|
public:
|
|
explicit ThreadSafeFS() : ProxyFileSystem(vfs::createPhysicalFileSystem()) {}
|
|
|
|
class VolatileFile : public vfs::File {
|
|
public:
|
|
VolatileFile(std::unique_ptr<vfs::File> Wrapped) : wrapped(std::move(Wrapped)) {
|
|
assert(this->wrapped);
|
|
}
|
|
|
|
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> getBuffer(const llvm::Twine& Name,
|
|
int64_t FileSize,
|
|
bool RequiresNullTerminator,
|
|
bool /*IsVolatile*/) override {
|
|
return wrapped->getBuffer(Name,
|
|
FileSize,
|
|
RequiresNullTerminator,
|
|
/*IsVolatile=*/true);
|
|
}
|
|
|
|
llvm::ErrorOr<vfs::Status> status() override {
|
|
return wrapped->status();
|
|
}
|
|
|
|
llvm::ErrorOr<std::string> getName() override {
|
|
return wrapped->getName();
|
|
}
|
|
|
|
std::error_code close() override {
|
|
return wrapped->close();
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<File> wrapped;
|
|
};
|
|
|
|
llvm::ErrorOr<std::unique_ptr<vfs::File>> openFileForRead(const llvm::Twine& InPath) override {
|
|
llvm::SmallString<128> Path;
|
|
InPath.toVector(Path);
|
|
|
|
auto file = getUnderlyingFS().openFileForRead(Path);
|
|
if(!file)
|
|
return file;
|
|
// Try to guess preamble files, they can be memory-mapped even on Windows as
|
|
// clangd has exclusive access to those and nothing else should touch them.
|
|
llvm::StringRef filename = path::filename(Path);
|
|
if(filename.starts_with("preamble-") && filename.ends_with(".pch")) {
|
|
return file;
|
|
}
|
|
return std::make_unique<VolatileFile>(std::move(*file));
|
|
}
|
|
};
|
|
|
|
} // namespace clice
|