Index: include/clang/Tooling/CompilationDatabase.h =================================================================== --- include/clang/Tooling/CompilationDatabase.h +++ include/clang/Tooling/CompilationDatabase.h @@ -91,7 +91,17 @@ /// 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); + loadFromDirectory(StringRef BuildDirectory, StringRef SourceRoot, + std::string &ErrorMessage); + + /// \brief Attempts to load a compilation database from the specified file. + /// + /// Returns NULL and sets ErrorMessage if the specified compilation database + /// does not exist or is in an unrecognized format. Relative paths will + /// be resolved starting from 'SourceRoot'. If 'SourceRoot' is empty + /// they will be resolved from the current working directory. + static std::unique_ptr + loadFromFile(StringRef File, StringRef SourceRoot, std::string &ErrorMessage); /// \brief Tries to detect a compilation database location and load it. /// @@ -102,10 +112,13 @@ /// \brief 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. + /// Looks for a compilation database in directory 'DatabaseDir' and all + /// its parent paths by calling loadFromDirectory. Relative paths will + /// be resolved starting from 'SourceRoot'. If 'SourceRoot' is empty + /// they will be resolved from the current working directory. static std::unique_ptr - autoDetectFromDirectory(StringRef SourceDir, std::string &ErrorMessage); + autoDetectFromDirectory(StringRef DatabaseDir, StringRef SourceRoot, + std::string &ErrorMessage); /// \brief Returns all compile commands in which the specified file was /// compiled. @@ -149,7 +162,15 @@ /// /// \see CompilationDatabase::loadFromDirectory(). virtual std::unique_ptr - loadFromDirectory(StringRef Directory, std::string &ErrorMessage) = 0; + loadFromDirectory(StringRef Directory, StringRef SourceRoot, + std::string &ErrorMessage) = 0; + + /// \brief Loads a compilation database from the specified file. + /// + /// \see CompilationDatabase::loadFromFile(). + virtual std::unique_ptr + loadFromFile(StringRef File, StringRef SourceRoot, + std::string &ErrorMessage) = 0; }; /// \brief A compilation database that returns a single compile command line. Index: include/clang/Tooling/JSONCompilationDatabase.h =================================================================== --- include/clang/Tooling/JSONCompilationDatabase.h +++ include/clang/Tooling/JSONCompilationDatabase.h @@ -62,7 +62,8 @@ /// Returns NULL and sets ErrorMessage if the database could not be /// loaded from the given file. static std::unique_ptr - loadFromFile(StringRef FilePath, std::string &ErrorMessage); + loadFromFile(StringRef FilePath, StringRef SourceRoot, + std::string &ErrorMessage); /// \brief Loads a JSON compilation database from a data buffer. /// @@ -89,8 +90,10 @@ private: /// \brief Constructs a JSON compilation database on a memory buffer. - JSONCompilationDatabase(std::unique_ptr Database) - : Database(std::move(Database)), + /// If a source root is given, it is used to resolve relative paths. + JSONCompilationDatabase(StringRef SourceRoot, + std::unique_ptr Database) + : Database(std::move(Database)), SourceRoot(SourceRoot), YAMLStream(this->Database->getBuffer(), SM) {} /// \brief Parses the database file and creates the index. @@ -113,6 +116,9 @@ void getCommands(ArrayRef CommandsRef, std::vector &Commands) const; + void getDirectoryForCommand(llvm::yaml::ScalarNode &JsonDir, + llvm::SmallVectorImpl &Dir) const; + // Maps file paths to the compile command lines for that file. llvm::StringMap> IndexByFile; @@ -123,6 +129,7 @@ FileMatchTrie MatchTrie; std::unique_ptr Database; + StringRef SourceRoot; llvm::SourceMgr SM; llvm::yaml::Stream YAMLStream; }; Index: lib/Tooling/CommonOptionsParser.cpp =================================================================== --- lib/Tooling/CommonOptionsParser.cpp +++ lib/Tooling/CommonOptionsParser.cpp @@ -36,14 +36,21 @@ "\n" "-p is used to read a compile command database.\n" "\n" - "\tFor example, it can be a CMake build directory in which a file named\n" - "\tcompile_commands.json exists (use -DCMAKE_EXPORT_COMPILE_COMMANDS=ON\n" - "\tCMake option to get this output). When no build path is specified,\n" + "\t can be either a file or a directory. If it is a file,\n" + "\tit will be treated as a JSON compile command database. If it is a\n" + "\tdirectory, the directory will be searched for a file named\n" + "\tcompile_commands.json (CMake, for example, generates such a file in\n" + "\tthe build output directory when used with the special option\n" + "\t-DCMAKE_EXPORT_COMPILE_COMMANDS=ON. When this option is not present,\n" "\ta search for compile_commands.json will be attempted through all\n" "\tparent paths of the first input file . See:\n" "\thttp://clang.llvm.org/docs/HowToSetupToolingForLLVM.html for an\n" "\texample of setting up Clang Tooling on a source tree.\n" "\n" + "-r is used to set the base folder from which to resolve\n" + "\trelative paths in the compile command database. By default relative\n" + "\tpaths are expected to be found relative to the current working\n" + "\tdirectory, as described below." " ... specify the paths of source files. These paths are\n" "\tlooked up in the compile command database. If the path of a file is\n" "\tabsolute, it needs to point into CMake's source tree. If the path is\n" @@ -99,7 +106,8 @@ static cl::opt BuildPath("p", cl::desc("Build path"), cl::Optional, cl::cat(Category)); - + static cl::opt SourceRoot("r", cl::desc("Source Root"), + cl::Optional, cl::cat(Category)); static cl::list SourcePaths( cl::Positional, cl::desc(" [... ]"), OccurrencesFlag, cl::cat(Category)); @@ -127,8 +135,16 @@ if (!Compilations) { std::string ErrorMessage; if (!BuildPath.empty()) { - Compilations = - CompilationDatabase::autoDetectFromDirectory(BuildPath, ErrorMessage); + if (llvm::sys::fs::is_regular_file(BuildPath)) { + Compilations = CompilationDatabase::loadFromFile(BuildPath, SourceRoot, + ErrorMessage); + if (!Compilations) + ErrorMessage = "Could not load compilation database \"" + BuildPath + + "\"\n" + ErrorMessage; + } else { + Compilations = CompilationDatabase::autoDetectFromDirectory( + BuildPath, SourceRoot, ErrorMessage); + } } else { Compilations = CompilationDatabase::autoDetectFromSource(SourcePaths[0], ErrorMessage); Index: lib/Tooling/CompilationDatabase.cpp =================================================================== --- lib/Tooling/CompilationDatabase.cpp +++ lib/Tooling/CompilationDatabase.cpp @@ -36,9 +36,8 @@ CompilationDatabase::~CompilationDatabase() {} -std::unique_ptr -CompilationDatabase::loadFromDirectory(StringRef BuildDirectory, - std::string &ErrorMessage) { +std::unique_ptr CompilationDatabase::loadFromDirectory( + StringRef BuildDirectory, StringRef SourceRoot, std::string &ErrorMessage) { std::stringstream ErrorStream; for (CompilationDatabasePluginRegistry::iterator It = CompilationDatabasePluginRegistry::begin(), @@ -46,8 +45,27 @@ It != Ie; ++It) { std::string DatabaseErrorMessage; std::unique_ptr Plugin(It->instantiate()); + if (std::unique_ptr DB = Plugin->loadFromDirectory( + BuildDirectory, SourceRoot, DatabaseErrorMessage)) + return DB; + ErrorStream << It->getName() << ": " << DatabaseErrorMessage << "\n"; + } + ErrorMessage = ErrorStream.str(); + return nullptr; +} + +std::unique_ptr +CompilationDatabase::loadFromFile(StringRef File, StringRef SourceRoot, + std::string &ErrorMessage) { + std::stringstream ErrorStream; + 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)) + Plugin->loadFromFile(File, SourceRoot, DatabaseErrorMessage)) return DB; ErrorStream << It->getName() << ": " << DatabaseErrorMessage << "\n"; } @@ -56,7 +74,7 @@ } static std::unique_ptr -findCompilationDatabaseFromDirectory(StringRef Directory, +findCompilationDatabaseFromDirectory(StringRef Directory, StringRef SourceRoot, std::string &ErrorMessage) { std::stringstream ErrorStream; bool HasErrorMessage = false; @@ -64,7 +82,8 @@ std::string LoadErrorMessage; if (std::unique_ptr DB = - CompilationDatabase::loadFromDirectory(Directory, LoadErrorMessage)) + CompilationDatabase::loadFromDirectory(Directory, SourceRoot, + LoadErrorMessage)) return DB; if (!HasErrorMessage) { @@ -86,21 +105,24 @@ StringRef Directory = llvm::sys::path::parent_path(AbsolutePath); std::unique_ptr DB = - findCompilationDatabaseFromDirectory(Directory, ErrorMessage); + findCompilationDatabaseFromDirectory(Directory, "", ErrorMessage); if (!DB) ErrorMessage = ("Could not auto-detect compilation database for file \"" + - SourceFile + "\"\n" + ErrorMessage).str(); + SourceFile + "\"\n" + ErrorMessage) + .str(); return DB; } std::unique_ptr CompilationDatabase::autoDetectFromDirectory(StringRef SourceDir, + StringRef SourceRoot, std::string &ErrorMessage) { SmallString<1024> AbsolutePath(getAbsolutePath(SourceDir)); std::unique_ptr DB = - findCompilationDatabaseFromDirectory(AbsolutePath, ErrorMessage); + findCompilationDatabaseFromDirectory(AbsolutePath, SourceRoot, + ErrorMessage); if (!DB) ErrorMessage = ("Could not auto-detect compilation database from directory \"" + Index: lib/Tooling/JSONCompilationDatabase.cpp =================================================================== --- lib/Tooling/JSONCompilationDatabase.cpp +++ lib/Tooling/JSONCompilationDatabase.cpp @@ -16,10 +16,12 @@ #include "clang/Tooling/CompilationDatabasePluginRegistry.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/Triple.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Path.h" #include "llvm/Support/StringSaver.h" +#include #include namespace clang { @@ -116,26 +118,34 @@ std::vector unescapeCommandLine( StringRef EscapedCommandLine) { -#if defined(LLVM_ON_WIN32) - llvm::BumpPtrAllocator Alloc; - llvm::StringSaver Saver(Alloc); - llvm::SmallVector T; - llvm::cl::TokenizeWindowsCommandLine(EscapedCommandLine, Saver, T); - std::vector Result(T.begin(), T.end()); - return Result; -#else - CommandLineArgumentParser parser(EscapedCommandLine); - return parser.parse(); -#endif + llvm::Triple Triple(llvm::sys::getDefaultTargetTriple()); + if (Triple.getOS() == llvm::Triple::OSType::Win32) { + llvm::BumpPtrAllocator Alloc; + llvm::StringSaver Saver(Alloc); + llvm::SmallVector T; + llvm::cl::TokenizeWindowsCommandLine(EscapedCommandLine, Saver, T); + std::vector Result(T.begin(), T.end()); + return Result; + } else { + CommandLineArgumentParser parser(EscapedCommandLine); + return parser.parse(); + } } class JSONCompilationDatabasePlugin : public CompilationDatabasePlugin { std::unique_ptr - loadFromDirectory(StringRef Directory, std::string &ErrorMessage) override { + loadFromDirectory(StringRef Directory, StringRef SourceRoot, + std::string &ErrorMessage) override { SmallString<1024> JSONDatabasePath(Directory); llvm::sys::path::append(JSONDatabasePath, "compile_commands.json"); + return loadFromFile(JSONDatabasePath, SourceRoot, ErrorMessage); + } + + std::unique_ptr + loadFromFile(StringRef File, StringRef SourceRoot, + std::string &ErrorMessage) override { std::unique_ptr Database( - JSONCompilationDatabase::loadFromFile(JSONDatabasePath, ErrorMessage)); + JSONCompilationDatabase::loadFromFile(File, SourceRoot, ErrorMessage)); if (!Database) return nullptr; return Database; @@ -154,7 +164,7 @@ volatile int JSONAnchorSource = 0; std::unique_ptr -JSONCompilationDatabase::loadFromFile(StringRef FilePath, +JSONCompilationDatabase::loadFromFile(StringRef FilePath, StringRef SourceRoot, std::string &ErrorMessage) { llvm::ErrorOr> DatabaseBuffer = llvm::MemoryBuffer::getFile(FilePath); @@ -163,7 +173,7 @@ return nullptr; } std::unique_ptr Database( - new JSONCompilationDatabase(std::move(*DatabaseBuffer))); + new JSONCompilationDatabase(SourceRoot, std::move(*DatabaseBuffer))); if (!Database->parse(ErrorMessage)) return nullptr; return Database; @@ -175,7 +185,7 @@ std::unique_ptr DatabaseBuffer( llvm::MemoryBuffer::getMemBuffer(DatabaseString)); std::unique_ptr Database( - new JSONCompilationDatabase(std::move(DatabaseBuffer))); + new JSONCompilationDatabase("", std::move(DatabaseBuffer))); if (!Database->parse(ErrorMessage)) return nullptr; return Database; @@ -239,12 +249,13 @@ ArrayRef CommandsRef, std::vector &Commands) const { for (int I = 0, E = CommandsRef.size(); I != E; ++I) { - SmallString<8> DirectoryStorage; + SmallString<128> DirectoryStorage; SmallString<32> FilenameStorage; + getDirectoryForCommand(*std::get<0>(CommandsRef[I]), DirectoryStorage); Commands.emplace_back( - std::get<0>(CommandsRef[I])->getValue(DirectoryStorage), - std::get<1>(CommandsRef[I])->getValue(FilenameStorage), - nodeToCommandLine(std::get<2>(CommandsRef[I]))); + DirectoryStorage, + std::get<1>(CommandsRef[I])->getValue(FilenameStorage), + nodeToCommandLine(std::get<2>(CommandsRef[I]))); } } @@ -337,14 +348,13 @@ StringRef FileName = File->getValue(FileStorage); SmallString<128> NativeFilePath; if (llvm::sys::path::is_relative(FileName)) { - SmallString<8> DirectoryStorage; - SmallString<128> AbsolutePath( - Directory->getValue(DirectoryStorage)); - llvm::sys::path::append(AbsolutePath, FileName); - llvm::sys::path::native(AbsolutePath, NativeFilePath); - } else { - llvm::sys::path::native(FileName, NativeFilePath); + SmallString<128> TempStorage(Directory->getValue(NativeFilePath)); + getDirectoryForCommand(*Directory, TempStorage); + llvm::sys::path::append(TempStorage, FileName); + FileStorage.swap(TempStorage); } + + llvm::sys::path::native(FileStorage, NativeFilePath); auto Cmd = CompileCommandRef(Directory, File, *Command); IndexByFile[NativeFilePath].push_back(Cmd); AllCommands.push_back(Cmd); @@ -353,5 +363,20 @@ return true; } +void JSONCompilationDatabase::getDirectoryForCommand( + llvm::yaml::ScalarNode &JsonDir, llvm::SmallVectorImpl &Dir) const { + SmallString<128> DirectoryStorage; + Dir.clear(); + StringRef JsonVal = JsonDir.getValue(DirectoryStorage); + Dir.append(JsonVal.begin(), JsonVal.end()); + if (llvm::sys::path::is_relative(Dir)) { + SmallString<128> TempStorage(SourceRoot); + llvm::sys::path::append(TempStorage, Dir); + Dir.swap(TempStorage); + } + llvm::sys::fs::make_absolute(Dir); + llvm::sys::path::native(Dir); +} + } // end namespace tooling } // end namespace clang Index: lib/Tooling/Tooling.cpp =================================================================== --- lib/Tooling/Tooling.cpp +++ lib/Tooling/Tooling.cpp @@ -407,10 +407,12 @@ // difference for example on network filesystems, where symlinks might be // switched during runtime of the tool. Fixing this depends on having a // file system abstraction that allows openat() style interactions. - if (OverlayFileSystem->setCurrentWorkingDirectory( - CompileCommand.Directory)) - llvm::report_fatal_error("Cannot chdir into \"" + - Twine(CompileCommand.Directory) + "\n!"); + if (!CompileCommand.Directory.empty()) { + if (OverlayFileSystem->setCurrentWorkingDirectory( + CompileCommand.Directory)) + llvm::report_fatal_error("Cannot chdir into \"" + + Twine(CompileCommand.Directory) + "\n!"); + } // Now fill the in-memory VFS with the relative file mappings so it will // have the correct relative paths. We never remove mappings but that