Index: include/clang/Tooling/CompilationDatabase.h =================================================================== --- include/clang/Tooling/CompilationDatabase.h +++ include/clang/Tooling/CompilationDatabase.h @@ -32,6 +32,7 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" +#include "llvm/Support/VirtualFileSystem.h" #include #include #include @@ -40,6 +41,25 @@ namespace clang { namespace tooling { +class CompilationDatabase; + +struct Codebase { + // The directory containing build artifacts. + std::string BuildRoot; + // Describes how to build the files in the codebase. + std::unique_ptr CompilationDatabase; + + // TODO: make the factory functions vfs-aware. + + // Detect the codebase containing the specified file or directory. + // Simply traverses upward until fromDirectory() returns a valid codebase. + static llvm::Optional detect(StringRef Path); + + // Returns information about the codebase rooted at a given directory. + // Root may be a build root or a source root (build root will be detected). + static llvm::Expected inDirectory(StringRef Root); +}; + /// Specifies the working directory and command of a compilation. struct CompileCommand { CompileCommand() = default; @@ -81,36 +101,6 @@ class CompilationDatabase { public: virtual ~CompilationDatabase(); - - /// Loads a compilation database from a build directory. - /// - /// Looks at the specified 'BuildDirectory' and creates a compilation database - /// that allows to query compile commands for source files in the - /// corresponding source tree. - /// - /// Returns NULL and sets ErrorMessage if we were not able to build up a - /// compilation database for the build directory. - /// - /// FIXME: Currently only supports JSON compilation databases, which - /// are named 'compile_commands.json' in the given directory. Extend this - /// for other build types (like ninja build files). - static std::unique_ptr - loadFromDirectory(StringRef BuildDirectory, std::string &ErrorMessage); - - /// Tries to detect a compilation database location and load it. - /// - /// Looks for a compilation database in all parent paths of file 'SourceFile' - /// by calling loadFromDirectory. - static std::unique_ptr - autoDetectFromSource(StringRef SourceFile, std::string &ErrorMessage); - - /// Tries to detect a compilation database location and load it. - /// - /// Looks for a compilation database in directory 'SourceDir' and all - /// its parent paths by calling loadFromDirectory. - static std::unique_ptr - autoDetectFromDirectory(StringRef SourceDir, std::string &ErrorMessage); - /// Returns all compile commands in which the specified file was /// compiled. /// Index: include/clang/Tooling/CompilationDatabasePluginRegistry.h =================================================================== --- include/clang/Tooling/CompilationDatabasePluginRegistry.h +++ include/clang/Tooling/CompilationDatabasePluginRegistry.h @@ -32,9 +32,11 @@ /// Loads a compilation database from a build directory. /// - /// \see CompilationDatabase::loadFromDirectory(). + /// If the database detects that Root is a source directory and the build + /// directory is external, it should replace Root with the build directory. + /// e.g. if compile_commands.json is a symlink. virtual std::unique_ptr - loadFromDirectory(StringRef Directory, std::string &ErrorMessage) = 0; + loadFromDirectory(StringRef &Root, std::string &ErrorMessage) = 0; }; using CompilationDatabasePluginRegistry = Index: lib/Tooling/CommonOptionsParser.cpp =================================================================== --- lib/Tooling/CommonOptionsParser.cpp +++ lib/Tooling/CommonOptionsParser.cpp @@ -130,16 +130,12 @@ SourcePathList.empty()) return llvm::Error::success(); if (!Compilations) { - if (!BuildPath.empty()) { - Compilations = - CompilationDatabase::autoDetectFromDirectory(BuildPath, ErrorMessage); + if (auto Codebase = + Codebase::detect(BuildPath.empty() ? SourcePaths[0] : BuildPath)) { + Compilations = std::move(Codebase->CompilationDatabase); } else { - Compilations = CompilationDatabase::autoDetectFromSource(SourcePaths[0], - ErrorMessage); - } - if (!Compilations) { - llvm::errs() << "Error while trying to load a compilation database:\n" - << ErrorMessage << "Running without flags.\n"; + llvm::errs() + << "Failed to detect compilation database. Running without flags.\n"; Compilations.reset( new FixedCompilationDatabase(".", std::vector())); } Index: lib/Tooling/CompilationDatabase.cpp =================================================================== --- lib/Tooling/CompilationDatabase.cpp +++ lib/Tooling/CompilationDatabase.cpp @@ -57,80 +57,41 @@ using namespace clang; using namespace tooling; -LLVM_INSTANTIATE_REGISTRY(CompilationDatabasePluginRegistry) - -CompilationDatabase::~CompilationDatabase() = default; - -std::unique_ptr -CompilationDatabase::loadFromDirectory(StringRef BuildDirectory, - std::string &ErrorMessage) { - llvm::raw_string_ostream ErrorStream(ErrorMessage); - for (CompilationDatabasePluginRegistry::iterator - It = CompilationDatabasePluginRegistry::begin(), - Ie = CompilationDatabasePluginRegistry::end(); - It != Ie; ++It) { - std::string DatabaseErrorMessage; - std::unique_ptr Plugin(It->instantiate()); - if (std::unique_ptr DB = - Plugin->loadFromDirectory(BuildDirectory, DatabaseErrorMessage)) - return DB; - ErrorStream << It->getName() << ": " << DatabaseErrorMessage << "\n"; - } - return nullptr; +llvm::Optional +Codebase::detect(StringRef Path) { + for (; !Path.empty(); Path = llvm::sys::path::parent_path(Path)) + if (auto Info = inDirectory(Path)) + return std::move(*Info); + else + consumeError(Info.takeError()); + return llvm::None; } -static std::unique_ptr -findCompilationDatabaseFromDirectory(StringRef Directory, - std::string &ErrorMessage) { - std::stringstream ErrorStream; - bool HasErrorMessage = false; - while (!Directory.empty()) { - std::string LoadErrorMessage; - - if (std::unique_ptr DB = - CompilationDatabase::loadFromDirectory(Directory, LoadErrorMessage)) - return DB; - - if (!HasErrorMessage) { - ErrorStream << "No compilation database found in " << Directory.str() - << " or any parent directory\n" << LoadErrorMessage; - HasErrorMessage = true; - } - - Directory = llvm::sys::path::parent_path(Directory); +llvm::Expected Codebase::inDirectory(StringRef Root) { + SmallString<256> BuildRoot = Root; + llvm::sys::path::append(BuildRoot, "buildroot"); + if (llvm::sys::fs::is_directory(BuildRoot)) + Root = BuildRoot; + + std::string Errs; + llvm::raw_string_ostream ErrsStream(Errs); + for (const auto& Plugin : CompilationDatabasePluginRegistry::entries()) { + std::string Err; + if (auto CDB = Plugin.instantiate()->loadFromDirectory(Root, Err)) { + Codebase Info; + Info.BuildRoot = Root; + Info.CompilationDatabase = std::move(CDB); + return Info; + } else + ErrsStream << Plugin.getName() << ": " << Err << "\n"; } - ErrorMessage = ErrorStream.str(); - return nullptr; + return llvm::make_error(ErrsStream.str(), + llvm::inconvertibleErrorCode()); } -std::unique_ptr -CompilationDatabase::autoDetectFromSource(StringRef SourceFile, - std::string &ErrorMessage) { - SmallString<1024> AbsolutePath(getAbsolutePath(SourceFile)); - StringRef Directory = llvm::sys::path::parent_path(AbsolutePath); - - std::unique_ptr DB = - findCompilationDatabaseFromDirectory(Directory, ErrorMessage); - - if (!DB) - ErrorMessage = ("Could not auto-detect compilation database for file \"" + - SourceFile + "\"\n" + ErrorMessage).str(); - return DB; -} - -std::unique_ptr -CompilationDatabase::autoDetectFromDirectory(StringRef SourceDir, - std::string &ErrorMessage) { - SmallString<1024> AbsolutePath(getAbsolutePath(SourceDir)); - - std::unique_ptr DB = - findCompilationDatabaseFromDirectory(AbsolutePath, ErrorMessage); +LLVM_INSTANTIATE_REGISTRY(CompilationDatabasePluginRegistry) - if (!DB) - ErrorMessage = ("Could not auto-detect compilation database from directory \"" + - SourceDir + "\"\n" + ErrorMessage).str(); - return DB; -} +CompilationDatabase::~CompilationDatabase() = default; std::vector CompilationDatabase::getAllCompileCommands() const { std::vector Result; @@ -386,7 +347,7 @@ class FixedCompilationDatabasePlugin : public CompilationDatabasePlugin { std::unique_ptr - loadFromDirectory(StringRef Directory, std::string &ErrorMessage) override { + loadFromDirectory(StringRef &Directory, std::string &ErrorMessage) override { SmallString<1024> DatabasePath(Directory); llvm::sys::path::append(DatabasePath, "compile_flags.txt"); return FixedCompilationDatabase::loadFromFile(DatabasePath, ErrorMessage); Index: lib/Tooling/JSONCompilationDatabase.cpp =================================================================== --- lib/Tooling/JSONCompilationDatabase.cpp +++ lib/Tooling/JSONCompilationDatabase.cpp @@ -24,6 +24,7 @@ #include "llvm/Support/Casting.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/ErrorOr.h" +#include "llvm/Support/FileSystem.h" #include "llvm/Support/Host.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" @@ -161,12 +162,19 @@ // compile commands for files not present in the database. class JSONCompilationDatabasePlugin : public CompilationDatabasePlugin { std::unique_ptr - loadFromDirectory(StringRef Directory, std::string &ErrorMessage) override { + loadFromDirectory(StringRef &Directory, std::string &ErrorMessage) override { SmallString<1024> JSONDatabasePath(Directory); llvm::sys::path::append(JSONDatabasePath, "compile_commands.json"); auto Base = JSONCompilationDatabase::loadFromFile( JSONDatabasePath, ErrorMessage, JSONCommandLineSyntax::AutoDetect); - return Base ? inferMissingCompileCommands(std::move(Base)) : nullptr; + if (!Base) + return nullptr; + // If compile_commands.json is a symlink, target's parent is the build root. + if (llvm::sys::fs::is_symlink_file(JSONDatabasePath)) { + llvm::sys::fs::real_path(JSONDatabasePath, JSONDatabasePath); + Directory = llvm::sys::path::parent_path(JSONDatabasePath); + } + return inferMissingCompileCommands(std::move(Base)); } }; Index: tools/libclang/CXCompilationDatabase.cpp =================================================================== --- tools/libclang/CXCompilationDatabase.cpp +++ tools/libclang/CXCompilationDatabase.cpp @@ -14,18 +14,18 @@ std::string ErrorMsg; CXCompilationDatabase_Error Err = CXCompilationDatabase_NoError; - std::unique_ptr db = - CompilationDatabase::loadFromDirectory(BuildDir, ErrorMsg); + auto Codebase = Codebase::inDirectory(BuildDir); - if (!db) { - fprintf(stderr, "LIBCLANG TOOLING ERROR: %s\n", ErrorMsg.c_str()); + if (!Codebase) { + fprintf(stderr, "LIBCLANG TOOLING ERROR: %s\n", + llvm::toString(Codebase.takeError()).c_str()); Err = CXCompilationDatabase_CanNotLoadDatabase; } if (ErrorCode) *ErrorCode = Err; - return db.release(); + return Codebase->CompilationDatabase.release(); } void