diff --git a/src/platform.hh b/src/platform.hh index 12446263..c673307d 100644 --- a/src/platform.hh +++ b/src/platform.hh @@ -18,6 +18,7 @@ limitations under the License. #include #include #include +#include namespace ccls { std::string NormalizePath(const std::string &path); @@ -32,4 +33,10 @@ std::string GetExternalCommandOutput(const std::vector &command, std::string_view input); void SpawnThread(void *(*fn)(void *), void *arg); + +std::optional TryMakeTempDirectory(); + +/// Removes a directory and all its contents. +void RemoveDirectoryRecursive(const std::string& path); + } // namespace ccls diff --git a/src/platform_posix.cc b/src/platform_posix.cc index 35d8d271..774f38d9 100644 --- a/src/platform_posix.cc +++ b/src/platform_posix.cc @@ -130,6 +130,27 @@ void SpawnThread(void *(*fn)(void *), void *arg) { pthread_create(&thd, &attr, fn, arg); pthread_attr_destroy(&attr); } + +/// callback function required for RemoveDirectoryRecursive() +static int nftwCallback(const char *name, const struct stat * /*unused*/, + int /*unused*/, FTW * /*unused*/) { + remove(name); + return 0; +} + +void RemoveDirectoryRecursive(const AbsolutePath &path) { + // https://stackoverflow.com/a/5467788/2192139 + nftw(path.path.c_str(), nftwCallback, 64, FTW_DEPTH | FTW_PHYS); +} + +optional TryMakeTempDirectory() { + char tmpl[] = "/tmp/ccls-XXXXXX"; + if(!mkdtemp(tmpl)) { + return nullopt; + } + return tmpl; +} + } // namespace ccls #endif diff --git a/src/platform_win.cc b/src/platform_win.cc index a2ca5185..cb852cf2 100644 --- a/src/platform_win.cc +++ b/src/platform_win.cc @@ -17,6 +17,7 @@ limitations under the License. #include "platform.hh" #include "utils.hh" +#include "log.hh" #include #include @@ -65,6 +66,47 @@ std::string GetExternalCommandOutput(const std::vector &command, void SpawnThread(void *(*fn)(void *), void *arg) { std::thread(fn, arg).detach(); } + +void RemoveDirectoryRecursive(const std::string &path) { + std::string pathString = path; // create modifiable copy + // parameter 'pFrom' must be double NULL-terminated: + pathString.append(2, 0); + + // We use SHFileOperation, because its FO_DELETE operation is recursive. + SHFILEOPSTRUCT op; + memset(&op, 0, sizeof(op)); + op.wFunc = FO_DELETE; + op.pFrom = pathString.c_str(); + op.fFlags = FOF_NO_UI; + SHFileOperation(&op); +} + +std::optional TryMakeTempDirectory() { + // get "temp" dir + char tmpdir_buf[MAX_PATH]; + DWORD len = GetTempPath(MAX_PATH, tmpdir_buf); + + // Unfortunately, there is no mkdtemp() on windows. We append a (random) + // GUID to use as a unique directory name. + GUID guid; + CoCreateGuid(&guid); + // simplest way to append the guid to the existing c string: + len += snprintf(tmpdir_buf + len, MAX_PATH - len, + "ccls-%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", + guid.Data1, guid.Data2, guid.Data3, + guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], + guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); + + std::string dirPath(tmpdir_buf, len); + + // finally, create the dir + const bool createSuccessful = (_mkdir(dirPath.c_str()) != -1) || + (errno == EEXIST); + + if(createSuccessful) return dirPath; + return std::nullopt; +} + } #endif diff --git a/src/project.cc b/src/project.cc index d7fa1ed9..d111b1b1 100644 --- a/src/project.cc +++ b/src/project.cc @@ -318,37 +318,41 @@ void Project::LoadDirectory(const std::string &root, Project::Folder &folder) { } else { // If `compilationDatabaseCommand` is specified, execute it to get the // compdb. -#ifdef _WIN32 - // TODO -#else - char tmpdir[] = "/tmp/ccls-compdb-XXXXXX"; - if (!mkdtemp(tmpdir)) - return; - CDBDir = tmpdir; + + // generally it would be nice if we could just let clang load the + // compilation database (compile_commands.json) from memory. + // However, clang insists on reading compile_commands.json from a + // directory, so we create a temporary directory just for clang to read + // from. + + auto tmpdir = TryMakeTempDirectory(); + if(!tmpdir.has_value()) { + LOG_S(ERROR) << "could not create temp directory for external " + "compile_commands.json command"; + return; + } + + CDBDir = tmpdir.value(); sys::path::append(Path, CDBDir, "compile_commands.json"); rapidjson::StringBuffer input; rapidjson::Writer writer(input); JsonWriter json_writer(&writer); Reflect(json_writer, *g_config); + LOG_S(INFO) << "Starting external command " << + g_config->compilationDatabaseCommand << " " << root; std::string contents = GetExternalCommandOutput( std::vector{g_config->compilationDatabaseCommand, root}, input.GetString()); FILE *fout = fopen(Path.c_str(), "wb"); fwrite(contents.c_str(), contents.size(), 1, fout); fclose(fout); -#endif } std::string err_msg; std::unique_ptr CDB = tooling::CompilationDatabase::loadFromDirectory(CDBDir, err_msg); if (!g_config->compilationDatabaseCommand.empty()) { -#ifdef _WIN32 - // TODO -#else - unlink(Path.c_str()); - rmdir(CDBDir.c_str()); -#endif + RemoveDirectoryRecursive(CDBDir.c_str()); } ProjectProcessor proc(folder);