Adds a FixedCompilationDatabase to be able to specify tool parameters
at the command line.



git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@154989 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/include/clang/Tooling/CompilationDatabase.h b/include/clang/Tooling/CompilationDatabase.h
index 60e9a08..625c8ec 100644
--- a/include/clang/Tooling/CompilationDatabase.h
+++ b/include/clang/Tooling/CompilationDatabase.h
@@ -33,6 +33,7 @@
 #include "llvm/ADT/OwningPtr.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/Twine.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/SourceMgr.h"
 #include "llvm/Support/YAMLParser.h"
@@ -45,8 +46,8 @@
 /// \brief Specifies the working directory and command of a compilation.
 struct CompileCommand {
   CompileCommand() {}
-  CompileCommand(StringRef Directory, ArrayRef<std::string> CommandLine)
-    : Directory(Directory), CommandLine(CommandLine) {}
+  CompileCommand(Twine Directory, ArrayRef<std::string> CommandLine)
+    : Directory(Directory.str()), CommandLine(CommandLine) {}
 
   /// \brief The working directory the command was executed from.
   std::string Directory;
@@ -93,6 +94,59 @@
     StringRef FilePath) const = 0;
 };
 
+/// \brief A compilation database that returns a single compile command line.
+///
+/// Useful when we want a tool to behave more like a compiler invocation.
+class FixedCompilationDatabase : public CompilationDatabase {
+public:
+  /// \brief Creates a FixedCompilationDatabase from the arguments after "--".
+  ///
+  /// Parses the given command line for "--". If "--" is found, the rest of
+  /// the arguments will make up the command line in the returned
+  /// FixedCompilationDatabase.
+  /// The arguments after "--" must not include positional parameters or the
+  /// argv[0] of the tool. Those will be added by the FixedCompilationDatabase
+  /// when a CompileCommand is requested. The argv[0] of the returned command
+  /// line will be "clang-tool".
+  ///
+  /// Returns NULL in case "--" is not found.
+  ///
+  /// The argument list is meant to be compatible with normal llvm command line
+  /// parsing in main methods.
+  /// int main(int argc, char **argv) {
+  ///   llvm::OwningPtr<FixedCompilationDatabase> Compilations(
+  ///     FixedCompilationDatabase::loadFromCommandLine(argc, argv));
+  ///   cl::ParseCommandLineOptions(argc, argv);
+  ///   ...
+  /// }
+  ///
+  /// \param Argc The number of command line arguments - will be changed to
+  /// the number of arguments before "--", if "--" was found in the argument
+  /// list.
+  /// \param Argv Points to the command line arguments.
+  /// \param Directory The base directory used in the FixedCompilationDatabase.
+  static FixedCompilationDatabase *loadFromCommandLine(int &Argc,
+                                                       const char **Argv,
+                                                       Twine Directory = ".");
+
+  /// \brief Constructs a compilation data base from a specified directory
+  /// and command line.
+  FixedCompilationDatabase(Twine Directory, ArrayRef<std::string> CommandLine);
+
+  /// \brief Returns the given compile command.
+  ///
+  /// Will always return a vector with one entry that contains the directory
+  /// and command line specified at construction with "clang-tool" as argv[0]
+  /// and 'FilePath' as positional argument.
+  virtual std::vector<CompileCommand> getCompileCommands(
+    StringRef FilePath) const;
+
+private:
+  /// This is built up to contain a single entry vector to be returned from
+  /// getCompileCommands after adding the positional argument.
+  std::vector<CompileCommand> CompileCommands;
+};
+
 /// \brief A JSON based compilation database.
 ///
 /// JSON compilation database files must contain a list of JSON objects which
@@ -112,7 +166,6 @@
 /// by setting the flag -DCMAKE_EXPORT_COMPILE_COMMANDS.
 class JSONCompilationDatabase : public CompilationDatabase {
 public:
-
   /// \brief Loads a JSON compilation database from the specified file.
   ///
   /// Returns NULL and sets ErrorMessage if the database could not be
diff --git a/lib/Tooling/CompilationDatabase.cpp b/lib/Tooling/CompilationDatabase.cpp
index f899365..dd9ccc0 100644
--- a/lib/Tooling/CompilationDatabase.cpp
+++ b/lib/Tooling/CompilationDatabase.cpp
@@ -121,6 +121,33 @@
   return Database.take();
 }
 
+FixedCompilationDatabase *
+FixedCompilationDatabase::loadFromCommandLine(int &Argc,
+                                              const char **Argv,
+                                              Twine Directory) {
+  const char **DoubleDash = std::find(Argv, Argv + Argc, StringRef("--"));
+  if (DoubleDash == Argv + Argc)
+    return NULL;
+  std::vector<std::string> CommandLine(DoubleDash + 1, Argv + Argc);
+  Argc = DoubleDash - Argv;
+  return new FixedCompilationDatabase(Directory, CommandLine);
+}
+
+FixedCompilationDatabase::
+FixedCompilationDatabase(Twine Directory, ArrayRef<std::string> CommandLine) {
+  std::vector<std::string> ToolCommandLine(1, "clang-tool");
+  ToolCommandLine.insert(ToolCommandLine.end(),
+                         CommandLine.begin(), CommandLine.end());
+  CompileCommands.push_back(CompileCommand(Directory, ToolCommandLine));
+}
+
+std::vector<CompileCommand>
+FixedCompilationDatabase::getCompileCommands(StringRef FilePath) const {
+  std::vector<CompileCommand> Result(CompileCommands);
+  Result[0].CommandLine.push_back(FilePath);
+  return Result;
+}
+
 JSONCompilationDatabase *
 JSONCompilationDatabase::loadFromFile(StringRef FilePath,
                                       std::string &ErrorMessage) {
diff --git a/test/Tooling/clang-check-args.cpp b/test/Tooling/clang-check-args.cpp
new file mode 100644
index 0000000..a14fc7b
--- /dev/null
+++ b/test/Tooling/clang-check-args.cpp
@@ -0,0 +1,8 @@
+// RUN: clang-check . "%s" -- -c 2>&1 | FileCheck %s
+
+// CHECK: C++ requires
+invalid;
+
+// FIXME: JSON doesn't like path separator '\', on Win32 hosts.
+// FIXME: clang-check doesn't like gcc driver on cygming.
+// XFAIL: cygwin,mingw32,win32
diff --git a/tools/clang-check/ClangCheck.cpp b/tools/clang-check/ClangCheck.cpp
index b5b6bd5..d68e282 100644
--- a/tools/clang-check/ClangCheck.cpp
+++ b/tools/clang-check/ClangCheck.cpp
@@ -50,13 +50,17 @@
   cl::desc("<source0> [... <sourceN>]"),
   cl::OneOrMore);
 
-int main(int argc, char **argv) {
-  cl::ParseCommandLineOptions(argc, argv);
-  std::string ErrorMessage;
+int main(int argc, const char **argv) {
   llvm::OwningPtr<CompilationDatabase> Compilations(
-    CompilationDatabase::loadFromDirectory(BuildPath, ErrorMessage));
-  if (!Compilations)
-    llvm::report_fatal_error(ErrorMessage);
+    FixedCompilationDatabase::loadFromCommandLine(argc, argv));
+  cl::ParseCommandLineOptions(argc, argv);
+  if (!Compilations) {
+    std::string ErrorMessage;
+    Compilations.reset(CompilationDatabase::loadFromDirectory(BuildPath,
+                                                              ErrorMessage));
+    if (!Compilations)
+      llvm::report_fatal_error(ErrorMessage);
+  }
   ClangTool Tool(*Compilations, SourcePaths);
   return Tool.run(newFrontendActionFactory<clang::SyntaxOnlyAction>());
 }
diff --git a/unittests/Tooling/CompilationDatabaseTest.cpp b/unittests/Tooling/CompilationDatabaseTest.cpp
index 9747de2..68d2896 100644
--- a/unittests/Tooling/CompilationDatabaseTest.cpp
+++ b/unittests/Tooling/CompilationDatabaseTest.cpp
@@ -219,5 +219,74 @@
   EXPECT_EQ("", Empty[0]);
 }
 
+TEST(FixedCompilationDatabase, ReturnsFixedCommandLine) {
+  std::vector<std::string> CommandLine;
+  CommandLine.push_back("one");
+  CommandLine.push_back("two");
+  FixedCompilationDatabase Database(".", CommandLine);
+  std::vector<CompileCommand> Result =
+    Database.getCompileCommands("source");
+  ASSERT_EQ(1ul, Result.size());
+  std::vector<std::string> ExpectedCommandLine(1, "clang-tool");
+  ExpectedCommandLine.insert(ExpectedCommandLine.end(),
+                             CommandLine.begin(), CommandLine.end());
+  ExpectedCommandLine.push_back("source");
+  EXPECT_EQ(".", Result[0].Directory);
+  EXPECT_EQ(ExpectedCommandLine, Result[0].CommandLine);
+}
+
+TEST(ParseFixedCompilationDatabase, ReturnsNullOnEmptyArgumentList) {
+  int Argc = 0;
+  llvm::OwningPtr<FixedCompilationDatabase> Database(
+      FixedCompilationDatabase::loadFromCommandLine(Argc, NULL));
+  EXPECT_FALSE(Database);
+  EXPECT_EQ(0, Argc);
+}
+
+TEST(ParseFixedCompilationDatabase, ReturnsNullWithoutDoubleDash) {
+  int Argc = 2;
+  const char *Argv[] = { "1", "2" };
+  llvm::OwningPtr<FixedCompilationDatabase> Database(
+      FixedCompilationDatabase::loadFromCommandLine(Argc, Argv));
+  EXPECT_FALSE(Database);
+  EXPECT_EQ(2, Argc);
+}
+
+TEST(ParseFixedCompilationDatabase, ReturnsArgumentsAfterDoubleDash) {
+  int Argc = 5;
+  const char *Argv[] = { "1", "2", "--\0no-constant-folding", "3", "4" };
+  llvm::OwningPtr<FixedCompilationDatabase> Database(
+      FixedCompilationDatabase::loadFromCommandLine(Argc, Argv));
+  ASSERT_TRUE(Database);
+  std::vector<CompileCommand> Result =
+    Database->getCompileCommands("source");
+  ASSERT_EQ(1ul, Result.size());
+  ASSERT_EQ(".", Result[0].Directory);
+  std::vector<std::string> CommandLine;
+  CommandLine.push_back("clang-tool");
+  CommandLine.push_back("3");
+  CommandLine.push_back("4");
+  CommandLine.push_back("source");
+  ASSERT_EQ(CommandLine, Result[0].CommandLine);
+  EXPECT_EQ(2, Argc);
+}
+
+TEST(ParseFixedCompilationDatabase, ReturnsEmptyCommandLine) {
+  int Argc = 3;
+  const char *Argv[] = { "1", "2", "--\0no-constant-folding" };
+  llvm::OwningPtr<FixedCompilationDatabase> Database(
+      FixedCompilationDatabase::loadFromCommandLine(Argc, Argv));
+  ASSERT_TRUE(Database);
+  std::vector<CompileCommand> Result =
+    Database->getCompileCommands("source");
+  ASSERT_EQ(1ul, Result.size());
+  ASSERT_EQ(".", Result[0].Directory);
+  std::vector<std::string> CommandLine;
+  CommandLine.push_back("clang-tool");
+  CommandLine.push_back("source");
+  ASSERT_EQ(CommandLine, Result[0].CommandLine);
+  EXPECT_EQ(2, Argc);
+}
+
 } // end namespace tooling
 } // end namespace clang