CompilationDatabase.cpp revision 4187df5f7bdda66d2fbe64a3f2660ee3977f0d12
1//===--- CompilationDatabase.cpp - ----------------------------------------===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10//  This file contains multiple implementations for CompilationDatabases.
11//
12//===----------------------------------------------------------------------===//
13
14#include "clang/Tooling/CompilationDatabase.h"
15#include "clang/Tooling/Tooling.h"
16#include "llvm/ADT/SmallString.h"
17#include "llvm/Support/YAMLParser.h"
18#include "llvm/Support/Path.h"
19#include "llvm/Support/system_error.h"
20
21namespace clang {
22namespace tooling {
23
24namespace {
25
26/// \brief A parser for escaped strings of command line arguments.
27///
28/// Assumes \-escaping for quoted arguments (see the documentation of
29/// unescapeCommandLine(...)).
30class CommandLineArgumentParser {
31 public:
32  CommandLineArgumentParser(StringRef CommandLine)
33      : Input(CommandLine), Position(Input.begin()-1) {}
34
35  std::vector<std::string> parse() {
36    bool HasMoreInput = true;
37    while (HasMoreInput && nextNonWhitespace()) {
38      std::string Argument;
39      HasMoreInput = parseStringInto(Argument);
40      CommandLine.push_back(Argument);
41    }
42    return CommandLine;
43  }
44
45 private:
46  // All private methods return true if there is more input available.
47
48  bool parseStringInto(std::string &String) {
49    do {
50      if (*Position == '"') {
51        if (!parseQuotedStringInto(String)) return false;
52      } else {
53        if (!parseFreeStringInto(String)) return false;
54      }
55    } while (*Position != ' ');
56    return true;
57  }
58
59  bool parseQuotedStringInto(std::string &String) {
60    if (!next()) return false;
61    while (*Position != '"') {
62      if (!skipEscapeCharacter()) return false;
63      String.push_back(*Position);
64      if (!next()) return false;
65    }
66    return next();
67  }
68
69  bool parseFreeStringInto(std::string &String) {
70    do {
71      if (!skipEscapeCharacter()) return false;
72      String.push_back(*Position);
73      if (!next()) return false;
74    } while (*Position != ' ' && *Position != '"');
75    return true;
76  }
77
78  bool skipEscapeCharacter() {
79    if (*Position == '\\') {
80      return next();
81    }
82    return true;
83  }
84
85  bool nextNonWhitespace() {
86    do {
87      if (!next()) return false;
88    } while (*Position == ' ');
89    return true;
90  }
91
92  bool next() {
93    ++Position;
94    return Position != Input.end();
95  }
96
97  const StringRef Input;
98  StringRef::iterator Position;
99  std::vector<std::string> CommandLine;
100};
101
102std::vector<std::string> unescapeCommandLine(
103    StringRef EscapedCommandLine) {
104  CommandLineArgumentParser parser(EscapedCommandLine);
105  return parser.parse();
106}
107
108} // end namespace
109
110CompilationDatabase::~CompilationDatabase() {}
111
112CompilationDatabase *
113CompilationDatabase::loadFromDirectory(StringRef BuildDirectory,
114                                       std::string &ErrorMessage) {
115  llvm::SmallString<1024> JSONDatabasePath(BuildDirectory);
116  llvm::sys::path::append(JSONDatabasePath, "compile_commands.json");
117  llvm::OwningPtr<CompilationDatabase> Database(
118    JSONCompilationDatabase::loadFromFile(JSONDatabasePath, ErrorMessage));
119  if (!Database) {
120    return NULL;
121  }
122  return Database.take();
123}
124
125static CompilationDatabase *
126findCompilationDatabaseFromDirectory(StringRef Directory) {
127  while (!Directory.empty()) {
128    std::string LoadErrorMessage;
129
130    if (CompilationDatabase *DB =
131           CompilationDatabase::loadFromDirectory(Directory, LoadErrorMessage))
132      return DB;
133
134    Directory = llvm::sys::path::parent_path(Directory);
135  }
136  return NULL;
137}
138
139CompilationDatabase *
140CompilationDatabase::autoDetectFromSource(StringRef SourceFile,
141                                          std::string &ErrorMessage) {
142  llvm::SmallString<1024> AbsolutePath(getAbsolutePath(SourceFile));
143  StringRef Directory = llvm::sys::path::parent_path(AbsolutePath);
144
145  CompilationDatabase *DB = findCompilationDatabaseFromDirectory(Directory);
146
147  if (!DB)
148    ErrorMessage = ("Could not auto-detect compilation database for file \"" +
149                   SourceFile + "\"").str();
150  return DB;
151}
152
153CompilationDatabase *
154CompilationDatabase::autoDetectFromDirectory(StringRef SourceDir,
155                                             std::string &ErrorMessage) {
156  llvm::SmallString<1024> AbsolutePath(getAbsolutePath(SourceDir));
157
158  CompilationDatabase *DB = findCompilationDatabaseFromDirectory(AbsolutePath);
159
160  if (!DB)
161    ErrorMessage = ("Could not auto-detect compilation database from directory \"" +
162                   SourceDir + "\"").str();
163  return DB;
164}
165
166FixedCompilationDatabase *
167FixedCompilationDatabase::loadFromCommandLine(int &Argc,
168                                              const char **Argv,
169                                              Twine Directory) {
170  const char **DoubleDash = std::find(Argv, Argv + Argc, StringRef("--"));
171  if (DoubleDash == Argv + Argc)
172    return NULL;
173  std::vector<std::string> CommandLine(DoubleDash + 1, Argv + Argc);
174  Argc = DoubleDash - Argv;
175  return new FixedCompilationDatabase(Directory, CommandLine);
176}
177
178FixedCompilationDatabase::
179FixedCompilationDatabase(Twine Directory, ArrayRef<std::string> CommandLine) {
180  std::vector<std::string> ToolCommandLine(1, "clang-tool");
181  ToolCommandLine.insert(ToolCommandLine.end(),
182                         CommandLine.begin(), CommandLine.end());
183  CompileCommands.push_back(CompileCommand(Directory, ToolCommandLine));
184}
185
186std::vector<CompileCommand>
187FixedCompilationDatabase::getCompileCommands(StringRef FilePath) const {
188  std::vector<CompileCommand> Result(CompileCommands);
189  Result[0].CommandLine.push_back(FilePath);
190  return Result;
191}
192
193JSONCompilationDatabase *
194JSONCompilationDatabase::loadFromFile(StringRef FilePath,
195                                      std::string &ErrorMessage) {
196  llvm::OwningPtr<llvm::MemoryBuffer> DatabaseBuffer;
197  llvm::error_code Result =
198    llvm::MemoryBuffer::getFile(FilePath, DatabaseBuffer);
199  if (Result != 0) {
200    ErrorMessage = "Error while opening JSON database: " + Result.message();
201    return NULL;
202  }
203  llvm::OwningPtr<JSONCompilationDatabase> Database(
204    new JSONCompilationDatabase(DatabaseBuffer.take()));
205  if (!Database->parse(ErrorMessage))
206    return NULL;
207  return Database.take();
208}
209
210JSONCompilationDatabase *
211JSONCompilationDatabase::loadFromBuffer(StringRef DatabaseString,
212                                        std::string &ErrorMessage) {
213  llvm::OwningPtr<llvm::MemoryBuffer> DatabaseBuffer(
214      llvm::MemoryBuffer::getMemBuffer(DatabaseString));
215  llvm::OwningPtr<JSONCompilationDatabase> Database(
216    new JSONCompilationDatabase(DatabaseBuffer.take()));
217  if (!Database->parse(ErrorMessage))
218    return NULL;
219  return Database.take();
220}
221
222std::vector<CompileCommand>
223JSONCompilationDatabase::getCompileCommands(StringRef FilePath) const {
224  llvm::SmallString<128> NativeFilePath;
225  llvm::sys::path::native(FilePath, NativeFilePath);
226  llvm::StringMap< std::vector<CompileCommandRef> >::const_iterator
227    CommandsRefI = IndexByFile.find(NativeFilePath);
228  if (CommandsRefI == IndexByFile.end())
229    return std::vector<CompileCommand>();
230  const std::vector<CompileCommandRef> &CommandsRef = CommandsRefI->getValue();
231  std::vector<CompileCommand> Commands;
232  for (int I = 0, E = CommandsRef.size(); I != E; ++I) {
233    llvm::SmallString<8> DirectoryStorage;
234    llvm::SmallString<1024> CommandStorage;
235    Commands.push_back(CompileCommand(
236      // FIXME: Escape correctly:
237      CommandsRef[I].first->getValue(DirectoryStorage),
238      unescapeCommandLine(CommandsRef[I].second->getValue(CommandStorage))));
239  }
240  return Commands;
241}
242
243bool JSONCompilationDatabase::parse(std::string &ErrorMessage) {
244  llvm::yaml::document_iterator I = YAMLStream.begin();
245  if (I == YAMLStream.end()) {
246    ErrorMessage = "Error while parsing YAML.";
247    return false;
248  }
249  llvm::yaml::Node *Root = I->getRoot();
250  if (Root == NULL) {
251    ErrorMessage = "Error while parsing YAML.";
252    return false;
253  }
254  llvm::yaml::SequenceNode *Array =
255    llvm::dyn_cast<llvm::yaml::SequenceNode>(Root);
256  if (Array == NULL) {
257    ErrorMessage = "Expected array.";
258    return false;
259  }
260  for (llvm::yaml::SequenceNode::iterator AI = Array->begin(),
261                                          AE = Array->end();
262       AI != AE; ++AI) {
263    llvm::yaml::MappingNode *Object =
264      llvm::dyn_cast<llvm::yaml::MappingNode>(&*AI);
265    if (Object == NULL) {
266      ErrorMessage = "Expected object.";
267      return false;
268    }
269    llvm::yaml::ScalarNode *Directory = NULL;
270    llvm::yaml::ScalarNode *Command = NULL;
271    llvm::yaml::ScalarNode *File = NULL;
272    for (llvm::yaml::MappingNode::iterator KVI = Object->begin(),
273                                           KVE = Object->end();
274         KVI != KVE; ++KVI) {
275      llvm::yaml::Node *Value = (*KVI).getValue();
276      if (Value == NULL) {
277        ErrorMessage = "Expected value.";
278        return false;
279      }
280      llvm::yaml::ScalarNode *ValueString =
281        llvm::dyn_cast<llvm::yaml::ScalarNode>(Value);
282      if (ValueString == NULL) {
283        ErrorMessage = "Expected string as value.";
284        return false;
285      }
286      llvm::yaml::ScalarNode *KeyString =
287        llvm::dyn_cast<llvm::yaml::ScalarNode>((*KVI).getKey());
288      if (KeyString == NULL) {
289        ErrorMessage = "Expected strings as key.";
290        return false;
291      }
292      llvm::SmallString<8> KeyStorage;
293      if (KeyString->getValue(KeyStorage) == "directory") {
294        Directory = ValueString;
295      } else if (KeyString->getValue(KeyStorage) == "command") {
296        Command = ValueString;
297      } else if (KeyString->getValue(KeyStorage) == "file") {
298        File = ValueString;
299      } else {
300        ErrorMessage = ("Unknown key: \"" +
301                        KeyString->getRawValue() + "\"").str();
302        return false;
303      }
304    }
305    if (!File) {
306      ErrorMessage = "Missing key: \"file\".";
307      return false;
308    }
309    if (!Command) {
310      ErrorMessage = "Missing key: \"command\".";
311      return false;
312    }
313    if (!Directory) {
314      ErrorMessage = "Missing key: \"directory\".";
315      return false;
316    }
317    llvm::SmallString<8> FileStorage;
318    llvm::SmallString<128> NativeFilePath;
319    llvm::sys::path::native(File->getValue(FileStorage), NativeFilePath);
320    IndexByFile[NativeFilePath].push_back(
321      CompileCommandRef(Directory, Command));
322  }
323  return true;
324}
325
326} // end namespace tooling
327} // end namespace clang
328