182 lines · cpp
1//===--- ConfigProvider.cpp - Loading of user configuration ---------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "ConfigProvider.h"10#include "Config.h"11#include "ConfigFragment.h"12#include "support/FileCache.h"13#include "support/Path.h"14#include "support/ThreadsafeFS.h"15#include "support/Trace.h"16#include "llvm/ADT/STLExtras.h"17#include "llvm/ADT/StringMap.h"18#include "llvm/ADT/StringRef.h"19#include "llvm/Support/Path.h"20#include <chrono>21#include <mutex>22#include <optional>23#include <string>24 25namespace clang {26namespace clangd {27namespace config {28 29// Threadsafe cache around reading a YAML config file from disk.30class FileConfigCache : public FileCache {31 mutable llvm::SmallVector<CompiledFragment, 1> CachedValue;32 std::string Directory;33 34public:35 FileConfigCache(llvm::StringRef Path, llvm::StringRef Directory)36 : FileCache(Path), Directory(Directory) {}37 38 void get(const ThreadsafeFS &TFS, DiagnosticCallback DC,39 std::chrono::steady_clock::time_point FreshTime, bool Trusted,40 std::vector<CompiledFragment> &Out) const {41 read(42 TFS, FreshTime,43 [&](std::optional<llvm::StringRef> Data) {44 CachedValue.clear();45 if (Data)46 for (auto &Fragment : Fragment::parseYAML(*Data, path(), DC)) {47 Fragment.Source.Directory = Directory;48 Fragment.Source.Trusted = Trusted;49 CachedValue.push_back(std::move(Fragment).compile(DC));50 }51 },52 [&]() { llvm::copy(CachedValue, std::back_inserter(Out)); });53 }54};55 56std::unique_ptr<Provider> Provider::fromYAMLFile(llvm::StringRef AbsPath,57 llvm::StringRef Directory,58 const ThreadsafeFS &FS,59 bool Trusted) {60 class AbsFileProvider : public Provider {61 mutable FileConfigCache Cache; // threadsafe62 const ThreadsafeFS &FS;63 bool Trusted;64 65 std::vector<CompiledFragment>66 getFragments(const Params &P, DiagnosticCallback DC) const override {67 std::vector<CompiledFragment> Result;68 Cache.get(FS, DC, P.FreshTime, Trusted, Result);69 return Result;70 };71 72 public:73 AbsFileProvider(llvm::StringRef Path, llvm::StringRef Directory,74 const ThreadsafeFS &FS, bool Trusted)75 : Cache(Path, Directory), FS(FS), Trusted(Trusted) {76 assert(llvm::sys::path::is_absolute(Path));77 }78 };79 80 return std::make_unique<AbsFileProvider>(AbsPath, Directory, FS, Trusted);81}82 83std::unique_ptr<Provider>84Provider::fromAncestorRelativeYAMLFiles(llvm::StringRef RelPath,85 const ThreadsafeFS &FS, bool Trusted) {86 class RelFileProvider : public Provider {87 std::string RelPath;88 const ThreadsafeFS &FS;89 bool Trusted;90 91 mutable std::mutex Mu;92 // Keys are the (posix-style) ancestor directory, not the config within it.93 // We only insert into this map, so pointers to values are stable forever.94 // Mutex guards the map itself, not the values (which are threadsafe).95 mutable llvm::StringMap<FileConfigCache> Cache;96 97 std::vector<CompiledFragment>98 getFragments(const Params &P, DiagnosticCallback DC) const override {99 namespace path = llvm::sys::path;100 101 if (P.Path.empty())102 return {};103 104 // Compute absolute paths to all ancestors (substrings of P.Path).105 llvm::SmallVector<llvm::StringRef, 8> Ancestors;106 for (auto Ancestor = absoluteParent(P.Path); !Ancestor.empty();107 Ancestor = absoluteParent(Ancestor)) {108 Ancestors.emplace_back(Ancestor);109 }110 // Ensure corresponding cache entries exist in the map.111 llvm::SmallVector<FileConfigCache *, 8> Caches;112 {113 std::lock_guard<std::mutex> Lock(Mu);114 for (llvm::StringRef Ancestor : Ancestors) {115 auto It = Cache.find(Ancestor);116 // Assemble the actual config file path only once.117 if (It == Cache.end()) {118 llvm::SmallString<256> ConfigPath = Ancestor;119 path::append(ConfigPath, RelPath);120 // Use native slashes for reading the file, affects diagnostics.121 llvm::sys::path::native(ConfigPath);122 It = Cache.try_emplace(Ancestor, ConfigPath.str(), Ancestor).first;123 }124 Caches.push_back(&It->second);125 }126 }127 // Finally query each individual file.128 // This will take a (per-file) lock for each file that actually exists.129 std::vector<CompiledFragment> Result;130 for (FileConfigCache *Cache : llvm::reverse(Caches))131 Cache->get(FS, DC, P.FreshTime, Trusted, Result);132 return Result;133 };134 135 public:136 RelFileProvider(llvm::StringRef RelPath, const ThreadsafeFS &FS,137 bool Trusted)138 : RelPath(RelPath), FS(FS), Trusted(Trusted) {139 assert(llvm::sys::path::is_relative(RelPath));140 }141 };142 143 return std::make_unique<RelFileProvider>(RelPath, FS, Trusted);144}145 146std::unique_ptr<Provider>147Provider::combine(std::vector<const Provider *> Providers) {148 class CombinedProvider : public Provider {149 std::vector<const Provider *> Providers;150 151 std::vector<CompiledFragment>152 getFragments(const Params &P, DiagnosticCallback DC) const override {153 std::vector<CompiledFragment> Result;154 for (const auto &Provider : Providers) {155 for (auto &Fragment : Provider->getFragments(P, DC))156 Result.push_back(std::move(Fragment));157 }158 return Result;159 }160 161 public:162 CombinedProvider(std::vector<const Provider *> Providers)163 : Providers(std::move(Providers)) {}164 };165 166 return std::make_unique<CombinedProvider>(std::move(Providers));167}168 169Config Provider::getConfig(const Params &P, DiagnosticCallback DC) const {170 trace::Span Tracer("getConfig");171 if (!P.Path.empty())172 SPAN_ATTACH(Tracer, "path", P.Path);173 Config C;174 for (const auto &Fragment : getFragments(P, DC))175 Fragment(P, C);176 return C;177}178 179} // namespace config180} // namespace clangd181} // namespace clang182