From 05f95d1018ed68c7708991d1f20dd3dabc1d4d64 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Fri, 21 Dec 2018 01:05:23 -0800 Subject: [PATCH] .ccls: add %h and %hpp; make .ccls augment compile_commands.json --- src/project.cc | 174 ++++++++++++++++++++++++------------------------- src/project.hh | 3 +- 2 files changed, 86 insertions(+), 91 deletions(-) diff --git a/src/project.cc b/src/project.cc index 5a4887657..45565c62f 100644 --- a/src/project.cc +++ b/src/project.cc @@ -67,13 +67,10 @@ std::pair lookupExtension(std::string_view filename) { namespace { -enum class ProjectMode { CompileCommandsJson, DotCcls, ExternalCommand }; - struct ProjectConfig { std::unordered_set quote_dirs; std::unordered_set angle_dirs; std::string root; - ProjectMode mode = ProjectMode::CompileCommandsJson; }; enum OptionClass { @@ -98,7 +95,7 @@ struct ProjectProcessor { // Expand %c %cpp %clang std::vector args; args.reserve(entry.args.size() + g_config->clang.extraArgs.size() + 1); - const LanguageId lang = lookupExtension(entry.filename).first; + auto [lang, header] = lookupExtension(entry.filename); for (const char *arg : entry.args) { StringRef A(arg); if (A == "%clang") { @@ -110,8 +107,12 @@ struct ProjectProcessor { for (;;) { if (A.consume_front("%c ")) ok |= lang == LanguageId::C; + else if (A.consume_front("%h ")) + ok |= lang == LanguageId::C && header; else if (A.consume_front("%cpp ")) ok |= lang == LanguageId::Cpp; + else if (A.consume_front("%hpp ")) + ok |= lang == LanguageId::Cpp && header; else if (A.consume_front("%objective-c ")) ok |= lang == LanguageId::ObjC; else if (A.consume_front("%objective-cpp ")) @@ -221,20 +222,19 @@ ReadCompilerArgumentsFromFile(const std::string &path) { return args; } -std::vector LoadFromDirectoryListing(ProjectConfig *config) { - std::vector result; - config->mode = ProjectMode::DotCcls; - +void LoadDirectoryListing(ProjectProcessor &proc, const StringSet<> &Seen, + std::vector &result) { + ProjectConfig *config = proc.config; std::unordered_map> folder_args; std::vector files; const std::string &root = config->root; - GetFilesInFolder(root, true /*recursive*/, - true /*add_folder_to_path*/, - [&folder_args, &files](const std::string &path) { + GetFilesInFolder(root, true /*recursive*/, true /*add_folder_to_path*/, + [&folder_args, &files, &Seen](const std::string &path) { std::pair lang = lookupExtension(path); if (lang.first != LanguageId::Unknown && !lang.second) { - files.push_back(path); + if (!Seen.count(path)) + files.push_back(path); } else if (sys::path::filename(path) == ".ccls") { std::vector args = ReadCompilerArgumentsFromFile(path); folder_args.emplace(sys::path::parent_path(path), args); @@ -263,7 +263,6 @@ std::vector LoadFromDirectoryListing(ProjectConfig *config) { return folder_args[root]; }; - ProjectProcessor proc(config); for (const std::string &file : files) { Project::Entry e; e.root = config->root; @@ -276,34 +275,43 @@ std::vector LoadFromDirectoryListing(ProjectConfig *config) { proc.Process(e); result.push_back(e); } - - return result; +} +// Computes a score based on how well |a| and |b| match. This is used for +// argument guessing. +int ComputeGuessScore(std::string_view a, std::string_view b) { + // Increase score based on common prefix and suffix. Prefixes are prioritized. + if (a.size() > b.size()) + std::swap(a, b); + size_t i = std::mismatch(a.begin(), a.end(), b.begin()).first - a.begin(); + size_t j = std::mismatch(a.rbegin(), a.rend(), b.rbegin()).first - a.rbegin(); + int score = 10 * i + j; + if (i + j < b.size()) + score -= 100 * (std::count(a.begin() + i, a.end() - j, '/') + + std::count(b.begin() + i, b.end() - j, '/')); + return score; } -std::vector -LoadEntriesFromDirectory(ProjectConfig *project, - const std::string &opt_compdb_dir) { - // If there is a .ccls file always load using directory listing. - SmallString<256> Path, CclsPath; - sys::path::append(CclsPath, project->root, ".ccls"); - if (sys::fs::exists(CclsPath)) - return LoadFromDirectoryListing(project); +} // namespace - // If |compilationDatabaseCommand| is specified, execute it to get the compdb. +void Project::LoadDirectory(const std::string &root, Project::Folder &folder) { + SmallString<256> Path; std::string comp_db_dir; + + folder.entries.clear(); if (g_config->compilationDatabaseCommand.empty()) { - project->mode = ProjectMode::CompileCommandsJson; - // Try to load compile_commands.json, but fallback to a project listing. - comp_db_dir = opt_compdb_dir.empty() ? project->root : opt_compdb_dir; + comp_db_dir = g_config->compilationDatabaseDirectory.size() + ? g_config->compilationDatabaseDirectory + : root; sys::path::append(Path, comp_db_dir, "compile_commands.json"); } else { - project->mode = ProjectMode::ExternalCommand; + // If `compilationDatabaseCommand` is specified, execute it to get the + // compdb. #ifdef _WIN32 // TODO #else char tmpdir[] = "/tmp/ccls-compdb-XXXXXX"; if (!mkdtemp(tmpdir)) - return {}; + return; comp_db_dir = tmpdir; sys::path::append(Path, comp_db_dir, "compile_commands.json"); rapidjson::StringBuffer input; @@ -312,7 +320,7 @@ LoadEntriesFromDirectory(ProjectConfig *project, Reflect(json_writer, *g_config); std::string contents = GetExternalCommandOutput( std::vector{g_config->compilationDatabaseCommand, - project->root}, + root}, input.GetString()); FILE *fout = fopen(Path.c_str(), "wb"); fwrite(contents.c_str(), contents.size(), 1, fout); @@ -331,79 +339,65 @@ LoadEntriesFromDirectory(ProjectConfig *project, rmdir(comp_db_dir.c_str()); #endif } - if (!CDB) { - LOG_S(WARNING) << "no .ccls or compile_commands.json . Consider adding one"; - return LoadFromDirectoryListing(project); - } - - LOG_S(INFO) << "loaded " << Path.c_str(); + ProjectConfig project; + project.root = root; + ProjectProcessor proc(&project); StringSet<> Seen; std::vector result; - ProjectProcessor proc(project); - for (tooling::CompileCommand &Cmd : CDB->getAllCompileCommands()) { - static bool once; - Project::Entry entry; - entry.root = project->root; - DoPathMapping(entry.root); - entry.directory = NormalizePath(Cmd.Directory); - DoPathMapping(entry.directory); - entry.filename = - NormalizePath(ResolveIfRelative(entry.directory, Cmd.Filename)); - DoPathMapping(entry.filename); - std::vector args = std::move(Cmd.CommandLine); - entry.args.reserve(args.size()); - for (std::string &arg : args) { - DoPathMapping(arg); - entry.args.push_back(Intern(arg)); - } + if (CDB) { + LOG_S(INFO) << "loaded " << Path.c_str(); + for (tooling::CompileCommand &Cmd : CDB->getAllCompileCommands()) { + static bool once; + Project::Entry entry; + entry.root = root; + DoPathMapping(entry.root); + entry.directory = NormalizePath(Cmd.Directory); + DoPathMapping(entry.directory); + entry.filename = + NormalizePath(ResolveIfRelative(entry.directory, Cmd.Filename)); + DoPathMapping(entry.filename); + std::vector args = std::move(Cmd.CommandLine); + entry.args.reserve(args.size()); + for (std::string &arg : args) { + DoPathMapping(arg); + entry.args.push_back(Intern(arg)); + } - // Work around relative --sysroot= as it isn't affected by - // -working-directory=. chdir is thread hostile but this function runs - // before indexers do actual work and it works when there is only one - // workspace folder. - if (!once) { - once = true; - llvm::vfs::getRealFileSystem()->setCurrentWorkingDirectory( - entry.directory); - } - proc.Process(entry); + // Work around relative --sysroot= as it isn't affected by + // -working-directory=. chdir is thread hostile but this function runs + // before indexers do actual work and it works when there is only one + // workspace folder. + if (!once) { + once = true; + llvm::vfs::getRealFileSystem()->setCurrentWorkingDirectory( + entry.directory); + } + proc.Process(entry); - if (Seen.insert(entry.filename).second) - result.push_back(entry); + if (Seen.insert(entry.filename).second) + folder.entries.push_back(entry); + } } - return result; -} -// Computes a score based on how well |a| and |b| match. This is used for -// argument guessing. -int ComputeGuessScore(std::string_view a, std::string_view b) { - // Increase score based on common prefix and suffix. Prefixes are prioritized. - if (a.size() > b.size()) - std::swap(a, b); - size_t i = std::mismatch(a.begin(), a.end(), b.begin()).first - a.begin(); - size_t j = std::mismatch(a.rbegin(), a.rend(), b.rbegin()).first - a.rbegin(); - int score = 10 * i + j; - if (i + j < b.size()) - score -= 100 * (std::count(a.begin() + i, a.end() - j, '/') + - std::count(b.begin() + i, b.end() - j, '/')); - return score; + // Use directory listing if .ccls exists or compile_commands.json does not + // exist. + Path.clear(); + sys::path::append(Path, root, ".ccls"); + if (!CDB || sys::fs::exists(Path)) + LoadDirectoryListing(proc, Seen, folder.entries); } -} // namespace - void Project::Load(const std::string &root) { assert(root.back() == '/'); - ProjectConfig project; - project.root = root; Folder &folder = root2folder[root]; - folder.entries = LoadEntriesFromDirectory( - &project, g_config->compilationDatabaseDirectory); - folder.quote_search_list.assign(project.quote_dirs.begin(), - project.quote_dirs.end()); - folder.angle_search_list.assign(project.angle_dirs.begin(), - project.angle_dirs.end()); + LoadDirectory(root, folder); + //folder.entries = LoadDirectory(root); + //folder.quote_search_list.assign(project.quote_dirs.begin(), + // project.quote_dirs.end()); + //folder.angle_search_list.assign(project.angle_dirs.begin(), + // project.angle_dirs.end()); for (std::string &path : folder.angle_search_list) { EnsureEndsInSlash(path); LOG_S(INFO) << "angle search: " << path; diff --git a/src/project.hh b/src/project.hh index a95951225..263ee34a0 100644 --- a/src/project.hh +++ b/src/project.hh @@ -63,7 +63,8 @@ struct Project { // will affect flags in their subtrees (relative paths are relative to the // project root, not subdirectories). For compile_commands.json, its entries // are indexed. - void Load(const std::string &root_directory); + void Load(const std::string &root); + void LoadDirectory(const std::string &root, Folder &folder); // Lookup the CompilationEntry for |filename|. If no entry was found this // will infer one based on existing project structure.