From cb971c6726d16e12ecd2a340941d7f5c06698332 Mon Sep 17 00:00:00 2001 From: Manuel Klimek Date: Wed, 4 Apr 2012 12:07:46 +0000 Subject: Adds a tooling library. Provides an API to run clang tools (FrontendActions) as standalone tools, or repeatedly in-memory in a process. This is useful for unit-testing, map-reduce style applications, source transformation daemons or command line tools. The ability to run over multiple translation units with different command line arguments enables building up refactoring tools that need to apply transformations across translation unit boundaries. See tools/clang-check/ClangCheck.cpp for an example. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@154008 91177308-0d34-0410-b5e6-96231b3b80d8 --- lib/Tooling/CompilationDatabase.cpp | 230 ++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 lib/Tooling/CompilationDatabase.cpp (limited to 'lib/Tooling/CompilationDatabase.cpp') diff --git a/lib/Tooling/CompilationDatabase.cpp b/lib/Tooling/CompilationDatabase.cpp new file mode 100644 index 0000000000..eea1055f49 --- /dev/null +++ b/lib/Tooling/CompilationDatabase.cpp @@ -0,0 +1,230 @@ +//===--- CompilationDatabase.cpp - ----------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file contains multiple implementations for CompilationDatabases. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/CompilationDatabase.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/JSONParser.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/system_error.h" + +namespace clang { +namespace tooling { + +namespace { + +/// \brief A parser for JSON escaped strings of command line arguments. +/// +/// Assumes \-escaping for quoted arguments (see the documentation of +/// unescapeJSONCommandLine(...)). +class CommandLineArgumentParser { + public: + CommandLineArgumentParser(StringRef CommandLine) + : Input(CommandLine), Position(Input.begin()-1) {} + + std::vector parse() { + bool HasMoreInput = true; + while (HasMoreInput && nextNonWhitespace()) { + std::string Argument; + HasMoreInput = parseStringInto(Argument); + CommandLine.push_back(Argument); + } + return CommandLine; + } + + private: + // All private methods return true if there is more input available. + + bool parseStringInto(std::string &String) { + do { + if (*Position == '"') { + if (!parseQuotedStringInto(String)) return false; + } else { + if (!parseFreeStringInto(String)) return false; + } + } while (*Position != ' '); + return true; + } + + bool parseQuotedStringInto(std::string &String) { + if (!next()) return false; + while (*Position != '"') { + if (!skipEscapeCharacter()) return false; + String.push_back(*Position); + if (!next()) return false; + } + return next(); + } + + bool parseFreeStringInto(std::string &String) { + do { + if (!skipEscapeCharacter()) return false; + String.push_back(*Position); + if (!next()) return false; + } while (*Position != ' ' && *Position != '"'); + return true; + } + + bool skipEscapeCharacter() { + if (*Position == '\\') { + return next(); + } + return true; + } + + bool nextNonWhitespace() { + do { + if (!next()) return false; + } while (*Position == ' '); + return true; + } + + bool next() { + ++Position; + if (Position == Input.end()) return false; + // Remove the JSON escaping first. This is done unconditionally. + if (*Position == '\\') ++Position; + return Position != Input.end(); + } + + const StringRef Input; + StringRef::iterator Position; + std::vector CommandLine; +}; + +std::vector unescapeJSONCommandLine( + StringRef JSONEscapedCommandLine) { + CommandLineArgumentParser parser(JSONEscapedCommandLine); + return parser.parse(); +} + +} // end namespace + +CompilationDatabase::~CompilationDatabase() {} + +CompilationDatabase * +CompilationDatabase::loadFromDirectory(StringRef BuildDirectory, + std::string &ErrorMessage) { + llvm::SmallString<1024> JSONDatabasePath(BuildDirectory); + llvm::sys::path::append(JSONDatabasePath, "compile_commands.json"); + llvm::OwningPtr Database( + JSONCompilationDatabase::loadFromFile(JSONDatabasePath, ErrorMessage)); + if (!Database) { + return NULL; + } + return Database.take(); +} + +JSONCompilationDatabase * +JSONCompilationDatabase::loadFromFile(StringRef FilePath, + std::string &ErrorMessage) { + llvm::OwningPtr DatabaseBuffer; + llvm::error_code Result = + llvm::MemoryBuffer::getFile(FilePath, DatabaseBuffer); + if (Result != 0) { + ErrorMessage = "Error while opening JSON database: " + Result.message(); + return NULL; + } + llvm::OwningPtr Database( + new JSONCompilationDatabase(DatabaseBuffer.take())); + if (!Database->parse(ErrorMessage)) + return NULL; + return Database.take(); +} + +JSONCompilationDatabase * +JSONCompilationDatabase::loadFromBuffer(StringRef DatabaseString, + std::string &ErrorMessage) { + llvm::OwningPtr DatabaseBuffer( + llvm::MemoryBuffer::getMemBuffer(DatabaseString)); + llvm::OwningPtr Database( + new JSONCompilationDatabase(DatabaseBuffer.take())); + if (!Database->parse(ErrorMessage)) + return NULL; + return Database.take(); +} + +std::vector +JSONCompilationDatabase::getCompileCommands(StringRef FilePath) const { + llvm::StringMap< std::vector >::const_iterator + CommandsRefI = IndexByFile.find(FilePath); + if (CommandsRefI == IndexByFile.end()) + return std::vector(); + const std::vector &CommandsRef = CommandsRefI->getValue(); + std::vector Commands; + for (int I = 0, E = CommandsRef.size(); I != E; ++I) { + Commands.push_back(CompileCommand( + // FIXME: Escape correctly: + CommandsRef[I].first, + unescapeJSONCommandLine(CommandsRef[I].second))); + } + return Commands; +} + +bool JSONCompilationDatabase::parse(std::string &ErrorMessage) { + llvm::SourceMgr SM; + llvm::JSONParser Parser(Database->getBuffer(), &SM); + llvm::JSONValue *Root = Parser.parseRoot(); + if (Root == NULL) { + ErrorMessage = "Error while parsing JSON."; + return false; + } + llvm::JSONArray *Array = dyn_cast(Root); + if (Array == NULL) { + ErrorMessage = "Expected array."; + return false; + } + for (llvm::JSONArray::const_iterator AI = Array->begin(), AE = Array->end(); + AI != AE; ++AI) { + const llvm::JSONObject *Object = dyn_cast(*AI); + if (Object == NULL) { + ErrorMessage = "Expected object."; + return false; + } + StringRef EntryDirectory; + StringRef EntryFile; + StringRef EntryCommand; + for (llvm::JSONObject::const_iterator KVI = Object->begin(), + KVE = Object->end(); + KVI != KVE; ++KVI) { + const llvm::JSONValue *Value = (*KVI)->Value; + if (Value == NULL) { + ErrorMessage = "Expected value."; + return false; + } + const llvm::JSONString *ValueString = + dyn_cast(Value); + if (ValueString == NULL) { + ErrorMessage = "Expected string as value."; + return false; + } + if ((*KVI)->Key->getRawText() == "directory") { + EntryDirectory = ValueString->getRawText(); + } else if ((*KVI)->Key->getRawText() == "file") { + EntryFile = ValueString->getRawText(); + } else if ((*KVI)->Key->getRawText() == "command") { + EntryCommand = ValueString->getRawText(); + } else { + ErrorMessage = (Twine("Unknown key: \"") + + (*KVI)->Key->getRawText() + "\"").str(); + return false; + } + } + IndexByFile[EntryFile].push_back( + CompileCommandRef(EntryDirectory, EntryCommand)); + } + return true; +} + +} // end namespace tooling +} // end namespace clang + -- cgit v1.2.3