From 4e10504a6dec600b96d23b049314d0e4fafada64 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sun, 10 Mar 2019 00:27:50 -0800 Subject: [PATCH] If the workspace folder is a symlink, convert paths relative to it (#314) If the workspace folder is a symlink and the client doesn't follow it. Treat /tmp/symlink/ as canonical and convert every /tmp/real/ path to /tmp/symlink/. --- src/clang_tu.cc | 11 ++--------- src/config.hh | 2 +- src/lsp.cc | 2 ++ src/messages/initialize.cc | 23 +++++++++++++++++------ src/messages/workspace.cc | 24 +++++++++++++++++------- src/pipeline.cc | 4 ++-- src/project.cc | 10 ++++++++-- src/utils.cc | 18 +++++++++++++++++- src/utils.hh | 2 ++ 9 files changed, 68 insertions(+), 28 deletions(-) diff --git a/src/clang_tu.cc b/src/clang_tu.cc index c64a5ea6..6dccba2e 100644 --- a/src/clang_tu.cc +++ b/src/clang_tu.cc @@ -30,15 +30,8 @@ std::string PathFromFileEntry(const FileEntry &file) { if (Name.empty()) Name = file.getName(); std::string ret = NormalizePath(Name); - // Resolve /usr/include/c++/7.3.0 symlink. - if (!llvm::any_of(g_config->workspaceFolders, [&](const std::string &root) { - return StringRef(ret).startswith(root); - })) { - SmallString<256> dest; - llvm::sys::fs::real_path(ret, dest); - ret = llvm::sys::path::convert_to_slash(dest.str()); - } - return ret; + // Resolve symlinks outside of workspace folders, e.g. /usr/include/c++/7.3.0 + return NormalizeFolder(ret) ? ret : RealPath(ret); } static Pos Decomposed2LineAndCol(const SourceManager &SM, diff --git a/src/config.hh b/src/config.hh index ac3a5baa..e4360660 100644 --- a/src/config.hh +++ b/src/config.hh @@ -32,7 +32,7 @@ initialization options specified by the client. For example, in shell syntax: struct Config { // **Not available for configuration** std::string fallbackFolder; - std::vector workspaceFolders; + std::vector> workspaceFolders; // If specified, this option overrides compile_commands.json and this // external command will be executed with an option |projectRoot|. // The initialization options will be provided as stdin. diff --git a/src/lsp.cc b/src/lsp.cc index f5e2f3e9..5c56f324 100644 --- a/src/lsp.cc +++ b/src/lsp.cc @@ -131,6 +131,8 @@ std::string DocumentUri::GetPath() const { ret[0] = toupper(ret[0]); } #endif + if (g_config) + NormalizeFolder(ret); return ret; } diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index 437895db..c0344a34 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -337,19 +337,30 @@ void Initialize(MessageHandler *m, InitializeParam ¶m, ReplyOnce &reply) { // Set project root. EnsureEndsInSlash(project_path); g_config->fallbackFolder = project_path; + auto &workspaceFolders = g_config->workspaceFolders; for (const WorkspaceFolder &wf : param.workspaceFolders) { std::string path = wf.uri.GetPath(); EnsureEndsInSlash(path); - g_config->workspaceFolders.push_back(path); - LOG_S(INFO) << "add workspace folder " << wf.name << ": " << path; + std::string real = RealPath(path) + '/'; + workspaceFolders.emplace_back(path, path == real ? "" : real); } - if (param.workspaceFolders.empty()) - g_config->workspaceFolders.push_back(project_path); + if (workspaceFolders.empty()) { + std::string real = RealPath(project_path) + '/'; + workspaceFolders.emplace_back(project_path, + project_path == real ? "" : real); + } + std::sort(workspaceFolders.begin(), workspaceFolders.end(), + [](auto &l, auto &r) { return l.first.size() > r.first.size(); }); + for (auto &[folder, real] : workspaceFolders) + if (real.empty()) + LOG_S(INFO) << "workspace folder: " << folder; + else + LOG_S(INFO) << "workspace folder: " << folder << " -> " << real; if (g_config->cache.directory.empty()) g_config->cache.retainInMemory = 1; else if (!g_config->cache.hierarchicalPath) - for (const std::string &folder : g_config->workspaceFolders) { + for (auto &[folder, _] : workspaceFolders) { // Create two cache directories for files inside and outside of the // project. std::string escaped = EscapeFileName(folder.substr(0, folder.size() - 1)); @@ -358,7 +369,7 @@ void Initialize(MessageHandler *m, InitializeParam ¶m, ReplyOnce &reply) { } idx::Init(); - for (const std::string &folder : g_config->workspaceFolders) + for (auto &[folder, _] : workspaceFolders) m->project->Load(folder); // Start indexer threads. Start this after loading the project, as that diff --git a/src/messages/workspace.cc b/src/messages/workspace.cc index 67e3e8a7..60ff01ae 100644 --- a/src/messages/workspace.cc +++ b/src/messages/workspace.cc @@ -35,7 +35,7 @@ namespace ccls { REFLECT_STRUCT(SymbolInformation, name, kind, location, containerName); void MessageHandler::workspace_didChangeConfiguration(EmptyParam &) { - for (const std::string &folder : g_config->workspaceFolders) + for (auto &[folder, _] : g_config->workspaceFolders) project->Load(folder); project->Index(wfiles, RequestId()); @@ -82,7 +82,8 @@ void MessageHandler::workspace_didChangeWorkspaceFolders( std::string root = wf.uri.GetPath(); EnsureEndsInSlash(root); LOG_S(INFO) << "delete workspace folder " << wf.name << ": " << root; - auto it = llvm::find(g_config->workspaceFolders, root); + auto it = llvm::find_if(g_config->workspaceFolders, + [&](auto &folder) { return folder.first == root; }); if (it != g_config->workspaceFolders.end()) { g_config->workspaceFolders.erase(it); { @@ -92,12 +93,21 @@ void MessageHandler::workspace_didChangeWorkspaceFolders( project->root2folder.erase(root); } } + auto &workspaceFolders = g_config->workspaceFolders; for (const WorkspaceFolder &wf : param.event.added) { - std::string root = wf.uri.GetPath(); - EnsureEndsInSlash(root); - LOG_S(INFO) << "add workspace folder " << wf.name << ": " << root; - g_config->workspaceFolders.push_back(root); - project->Load(root); + std::string folder = wf.uri.GetPath(); + EnsureEndsInSlash(folder); + std::string real = RealPath(folder) + '/'; + if (folder == real) + real.clear(); + LOG_S(INFO) << "add workspace folder " << wf.name << ": " + << (real.empty() ? folder : folder + " -> " + real); + workspaceFolders.emplace_back(); + auto it = workspaceFolders.end() - 1; + for (; it != workspaceFolders.begin() && folder < it[-1].first; --it) + *it = it[-1]; + *it = {folder, real}; + project->Load(folder); } project->Index(wfiles, RequestId()); diff --git a/src/pipeline.cc b/src/pipeline.cc index 4c1070e9..be01b9d8 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -145,7 +145,7 @@ std::string AppendSerializationFormat(const std::string &base) { } } -std::string GetCachePath(const std::string &src) { +std::string GetCachePath(std::string src) { if (g_config->cache.hierarchicalPath) { std::string ret = src[0] == '/' ? src.substr(1) : src; #ifdef _WIN32 @@ -153,7 +153,7 @@ std::string GetCachePath(const std::string &src) { #endif return g_config->cache.directory + ret; } - for (auto &root : g_config->workspaceFolders) + for (auto &[root, _] : g_config->workspaceFolders) if (StringRef(src).startswith(root)) { auto len = root.size(); return g_config->cache.directory + diff --git a/src/project.cc b/src/project.cc index d62f71dd..25bfa34c 100644 --- a/src/project.cc +++ b/src/project.cc @@ -400,11 +400,17 @@ void Project::LoadDirectory(const std::string &root, Project::Folder &folder) { Project::Entry entry; entry.root = root; DoPathMapping(entry.root); - entry.directory = NormalizePath(Cmd.Directory); + + // If workspace folder is real/ but entries use symlink/, convert to + // real/. + entry.directory = RealPath(Cmd.Directory); + NormalizeFolder(entry.directory); DoPathMapping(entry.directory); entry.filename = - NormalizePath(ResolveIfRelative(entry.directory, Cmd.Filename)); + RealPath(ResolveIfRelative(entry.directory, Cmd.Filename)); + NormalizeFolder(entry.filename); DoPathMapping(entry.filename); + std::vector args = std::move(Cmd.CommandLine); entry.args.reserve(args.size()); for (std::string &arg : args) { diff --git a/src/utils.cc b/src/utils.cc index 46728522..642f4e24 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -25,7 +25,6 @@ limitations under the License. #include #include #include -using namespace llvm; #include #include @@ -36,6 +35,8 @@ using namespace llvm; #include #include +using namespace llvm; + namespace ccls { struct Matcher::Impl { std::regex regex; @@ -142,6 +143,21 @@ std::string ResolveIfRelative(const std::string &directory, return NormalizePath(Ret.str()); } +std::string RealPath(const std::string &path) { + SmallString<256> buf; + sys::fs::real_path(path, buf); + return buf.empty() ? path : llvm::sys::path::convert_to_slash(buf); +} + +bool NormalizeFolder(std::string &path) { + for (auto &[root, real] : g_config->workspaceFolders) + if (real.size() && llvm::StringRef(path).startswith(real)) { + path = root + path.substr(real.size()); + return true; + } + return false; +} + std::optional LastWriteTime(const std::string &path) { sys::fs::file_status Status; if (sys::fs::status(path, Status)) diff --git a/src/utils.hh b/src/utils.hh index fcda9d8e..6485d44a 100644 --- a/src/utils.hh +++ b/src/utils.hh @@ -62,6 +62,8 @@ std::string EscapeFileName(std::string path); std::string ResolveIfRelative(const std::string &directory, const std::string &path); +std::string RealPath(const std::string &path); +bool NormalizeFolder(std::string &path); std::optional LastWriteTime(const std::string &path); std::optional ReadContent(const std::string &filename);