diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp --- a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp +++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp @@ -18,7 +18,9 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/FileUtilities.h" #include "llvm/Support/Path.h" +#include "llvm/Support/Program.h" #include #include #include @@ -63,14 +65,86 @@ } // namespace -static std::string getFallbackClangPath() { - static int Dummy; - std::string ClangdExecutable = - llvm::sys::fs::getMainExecutable("clangd", (void *)&Dummy); - SmallString<128> ClangPath; - ClangPath = llvm::sys::path::parent_path(ClangdExecutable); - llvm::sys::path::append(ClangPath, "clang"); - return ClangPath.str(); +// On Mac, `which clang` is /usr/bin/clang. It runs `xcrun clang`, which knows +// where the real clang is kept. We need to do the same thing, +// because cc1 (not the driver!) will find libc++ relative to argv[0]. +static llvm::Optional getMacClangPath() { +#ifndef __APPLE__ + return llvm::None; +#endif + + auto Xcrun = llvm::sys::findProgramByName("xcrun"); + if (!Xcrun) { + log("Couldn't find xcrun. Hopefully you have a non-apple toolchain..."); + return llvm::None; + } + llvm::SmallString<64> OutFile; + llvm::sys::fs::createTemporaryFile("clangd-xcrun", "", OutFile); + llvm::FileRemover OutRemover(OutFile); + llvm::Optional Redirects[3] = { + /*stdin=*/{""}, /*stdout=*/{OutFile}, /*stderr=*/{""}}; + vlog("Invoking {0} to find fallback clang path", *Xcrun); + int Ret = llvm::sys::ExecuteAndWait(*Xcrun, {"xcrun", "--find", "clang"}, + /*Env=*/llvm::None, Redirects, + /*SecondsToWait=*/10); + if (Ret != 0) { + log("xcrun exists but failed with code {0}. " + "If you have a non-apple toolchain, this is OK. " + "Otherwise, try xcode-select --install.", + Ret); + return llvm::None; + } + + auto Buf = llvm::MemoryBuffer::getFile(OutFile); + if (!Buf) { + log("Can't read xcrun output: {0}", Buf.getError().message()); + return llvm::None; + } + StringRef Path = Buf->get()->getBuffer().trim(); + if (Path.empty()) { + log("xcrun produced no output"); + return llvm::None; + } + return Path.str(); +} + +// Resolve symlinks if possible. +static std::string resolve(std::string Path) { + llvm::SmallString<128> Resolved; + if (llvm::sys::fs::real_path(Path, Resolved)) + return Path; // On error; + return Resolved.str(); +} + +// Get a plausible `clang` to use in the fallback compile command. +// FIXME: should we also use this if compile_commands.json has just `clang`? +static StringRef getFallbackClangPath() { + static const std::string &MemoizedFallbackPath = [&]() -> std::string { + // The driver and/or cc1 sometimes depend on the binary name to compute + // useful things like the standard library location. + // We need to emulate what clang on this system is likely to see. + // cc1 in particular looks at the "real path" of the running process, and + // so if /usr/bin/clang is a symlink, it sees the resolved path. + // clangd doesn't have that luxury, so we resolve symlinks ourselves. + + // /usr/bin/clang on a mac is a program that redirects to the right clang. + // We resolve it as if it were a symlink. + if (auto MacClang = getMacClangPath()) + return resolve(std::move(*MacClang)); + // On other platforms, just look for compilers on the PATH. + for (const char* Name : {"clang", "gcc", "ccc"}) + if (auto PathCC = llvm::sys::findProgramByName(Name)) + return resolve(std::move(*PathCC)); + // Fallback: a nonexistent 'clang' binary next to clangd. + static int Dummy; + std::string ClangdExecutable = + llvm::sys::fs::getMainExecutable("clangd", (void *)&Dummy); + SmallString<128> ClangPath; + ClangPath = llvm::sys::path::parent_path(ClangdExecutable); + llvm::sys::path::append(ClangPath, "clang"); + return ClangPath.str(); + }(); + return MemoizedFallbackPath; } tooling::CompileCommand