Hi,

we're working on C++ tools based on clang to get the power of
automated refactoring and analysis that Java has known for years to
C++ developers.

With the attached patch I include a proposal for how we could export
the compile command line for C/C++ files from cmake for Unix makefile
generators:
The idea is to output a small JSON database with all C/C++ files in
the project, which contains
- the file's path
- the working directory in which the compile runs
- the compile command

Background:
While C++ refactoring and analysis tools are not compilers, and thus
don't run as part of the build system, they need the exact information
of a build in order to be able to correctly understand the C++ code of
the project. The natural candidate to export that information is the
build system. We chose cmake to provide a first implementation, as
cmake is the current open source standard build system for large scale
C++ projects (like clang itself), and as such it is in the best
position to define a standard way to make compile commands accessible
for tools in the open source world.

Example tools:
- running clang -fsyntax-only over source code from an editor to get
fast syntax checks
- running match/replace tools over C++ code (a project we're currently
working on)

Any feedback would be highly appreciated. If you think the way this is
implemented is a dumb idea (which it probably is), please tell me - I
don't have a lot of experience in the cmake architecture. Also, if you
object to the idea of exporting this information in general, please
let me know.

Thanks a lot,
/Manuel
diff --git a/Source/cmGlobalUnixMakefileGenerator3.cxx b/Source/cmGlobalUnixMakefileGenerator3.cxx
index d9a341c..92f87c9 100644
--- a/Source/cmGlobalUnixMakefileGenerator3.cxx
+++ b/Source/cmGlobalUnixMakefileGenerator3.cxx
@@ -31,6 +31,7 @@ cmGlobalUnixMakefileGenerator3::cmGlobalUnixMakefileGenerator3()
 #else
   this->UseLinkScript = true;
 #endif
+  this->CommandDatabase = NULL;
 }
 
 void cmGlobalUnixMakefileGenerator3
@@ -139,6 +140,17 @@ void cmGlobalUnixMakefileGenerator3
 }
 
 //----------------------------------------------------------------------------
+std::string EscapeJSON(const std::string& s) {
+  std::string result;
+  for (int i = 0; i < s.size(); ++i) {
+    if (s[i] == '"' || s[i] == '\\') {
+      result += '\\';
+    }
+    result += s[i];
+  }
+  return result;
+}
+
 void cmGlobalUnixMakefileGenerator3::Generate()
 {
   // first do superclass method
@@ -189,6 +201,35 @@ void cmGlobalUnixMakefileGenerator3::Generate()
   // write the main makefile
   this->WriteMainMakefile2();
   this->WriteMainCMakefile();
+
+  if (this->CommandDatabase != NULL) {
+    *this->CommandDatabase << std::endl << "]";
+    delete this->CommandDatabase;
+    this->CommandDatabase = NULL;
+  }
+}
+
+void cmGlobalUnixMakefileGenerator3::AddCXXCompileCommand(
+    const std::string &sourceFile, const std::string &workingDirectory,
+    const std::string &compileCommand) {
+  if (this->CommandDatabase == NULL)
+    {
+    std::string commandDatabaseName =
+      std::string(this->GetCMakeInstance()->GetHomeOutputDirectory())
+      + "/cxx_commands.json";
+    this->CommandDatabase =
+      new cmGeneratedFileStream(commandDatabaseName.c_str());
+    *this->CommandDatabase << "[" << std::endl;
+    } else {
+    *this->CommandDatabase << "," << std::endl;
+    }
+  *this->CommandDatabase << "{" << std::endl
+      << "  \"directory\": \"" << EscapeJSON(workingDirectory) << "\","
+      << std::endl
+      << "  \"command\": \"" << EscapeJSON(compileCommand) << "\","
+      << std::endl
+      << "  \"file\": \"" << EscapeJSON(sourceFile) << "\""
+      << std::endl << "}";
 }
 
 void cmGlobalUnixMakefileGenerator3::WriteMainMakefile2()
diff --git a/Source/cmGlobalUnixMakefileGenerator3.h b/Source/cmGlobalUnixMakefileGenerator3.h
index cdc9460..a152e01 100644
--- a/Source/cmGlobalUnixMakefileGenerator3.h
+++ b/Source/cmGlobalUnixMakefileGenerator3.h
@@ -112,6 +112,10 @@ public:
   /** Record per-target progress information.  */
   void RecordTargetProgress(cmMakefileTargetGenerator* tg);
 
+  void AddCXXCompileCommand(const std::string &sourceFile,
+                            const std::string &workingDirectory,
+                            const std::string &compileCommand);
+
 protected:
   void WriteMainMakefile2();
   void WriteMainCMakefile();
@@ -178,6 +182,8 @@ protected:
   size_t CountProgressMarksInTarget(cmTarget* target,
                                     std::set<cmTarget*>& emitted);
   size_t CountProgressMarksInAll(cmLocalUnixMakefileGenerator3* lg);
+
+  cmGeneratedFileStream *CommandDatabase;
 };
 
 #endif
diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx
index 9dcd8f1..c14b789 100644
--- a/Source/cmMakefileTargetGenerator.cxx
+++ b/Source/cmMakefileTargetGenerator.cxx
@@ -249,6 +249,62 @@ void cmMakefileTargetGenerator::WriteCommonCodeRules()
 }
 
 //----------------------------------------------------------------------------
+std::string cmMakefileTargetGenerator::GetFlags(const std::string &l) {
+  std::string flags;
+  const char *lang = l.c_str();
+
+  bool shared = ((this->Target->GetType() == cmTarget::SHARED_LIBRARY) ||
+                 (this->Target->GetType() == cmTarget::MODULE_LIBRARY));
+
+  // Add language feature flags.
+  this->AddFeatureFlags(flags, lang);
+
+  this->LocalGenerator->AddArchitectureFlags(flags, this->Target,
+                                             lang, this->ConfigName);
+
+  // Fortran-specific flags computed for this target.
+  if(l == "Fortran")
+    {
+    this->AddFortranFlags(flags);
+    }
+
+  // Add shared-library flags if needed.
+  this->LocalGenerator->AddSharedFlags(flags, lang, shared);
+
+  // Add include directory flags.
+  this->LocalGenerator->
+    AppendFlags(flags, this->LocalGenerator->GetIncludeFlags(lang));
+  // Add include directory flags.
+  this->LocalGenerator->
+    AppendFlags(flags,this->GetFrameworkFlags().c_str());
+
+  return flags;
+}
+
+std::string cmMakefileTargetGenerator::GetDefines(const std::string &l) {
+  std::string defines;
+  const char *lang = l.c_str();
+  // Add the export symbol definition for shared library objects.
+  if(const char* exportMacro = this->Target->GetExportMacro())
+    {
+    this->LocalGenerator->AppendDefines(defines, exportMacro, lang);
+    }
+
+  // Add preprocessor definitions for this target and configuration.
+  this->LocalGenerator->AppendDefines
+    (defines, this->Makefile->GetProperty("COMPILE_DEFINITIONS"), lang);
+  this->LocalGenerator->AppendDefines
+    (defines, this->Target->GetProperty("COMPILE_DEFINITIONS"), lang);
+  std::string defPropName = "COMPILE_DEFINITIONS_";
+  defPropName +=
+    cmSystemTools::UpperCase(this->LocalGenerator->ConfigurationName);
+  this->LocalGenerator->AppendDefines
+    (defines, this->Makefile->GetProperty(defPropName.c_str()), lang);
+  this->LocalGenerator->AppendDefines
+    (defines, this->Target->GetProperty(defPropName.c_str()), lang);
+  return defines;
+}
+
 void cmMakefileTargetGenerator::WriteTargetLanguageFlags()
 {
   // write language flags for target
@@ -262,68 +318,22 @@ void cmMakefileTargetGenerator::WriteTargetLanguageFlags()
     cmStdString compiler = "CMAKE_";
     compiler += *l;
     compiler += "_COMPILER";
-    *this->FlagFileStream << "# compile " << l->c_str() << " with " << 
+    *this->FlagFileStream << "# compile " << l->c_str() << " with " <<
       this->Makefile->GetSafeDefinition(compiler.c_str()) << "\n";
     }
 
   for(std::set<cmStdString>::const_iterator l = languages.begin();
       l != languages.end(); ++l)
     {
-    const char *lang = l->c_str();
-    std::string flags;
-    std::string defines;
-    bool shared = ((this->Target->GetType() == cmTarget::SHARED_LIBRARY) ||
-                   (this->Target->GetType() == cmTarget::MODULE_LIBRARY));
-
-    // Add the export symbol definition for shared library objects.
-    if(const char* exportMacro = this->Target->GetExportMacro())
-      {
-      this->LocalGenerator->AppendDefines(defines, exportMacro, lang);
-      }
-
-    // Add preprocessor definitions for this target and configuration.
-    this->LocalGenerator->AppendDefines
-      (defines, this->Makefile->GetProperty("COMPILE_DEFINITIONS"), lang);
-    this->LocalGenerator->AppendDefines
-      (defines, this->Target->GetProperty("COMPILE_DEFINITIONS"), lang);
-    std::string defPropName = "COMPILE_DEFINITIONS_";
-    defPropName +=
-      cmSystemTools::UpperCase(this->LocalGenerator->ConfigurationName);
-    this->LocalGenerator->AppendDefines
-      (defines, this->Makefile->GetProperty(defPropName.c_str()), lang);
-    this->LocalGenerator->AppendDefines
-      (defines, this->Target->GetProperty(defPropName.c_str()), lang);
-
-    // Add language feature flags.
-    this->AddFeatureFlags(flags, lang);
-
-    this->LocalGenerator->AddArchitectureFlags(flags, this->Target,
-                                               lang, this->ConfigName);
-
-    // Fortran-specific flags computed for this target.
-    if(*l == "Fortran")
-      {
-      this->AddFortranFlags(flags);
-      }
-
-    // Add shared-library flags if needed.
-    this->LocalGenerator->AddSharedFlags(flags, lang, shared);
-
-    // Add include directory flags.
-    this->LocalGenerator->
-      AppendFlags(flags, this->LocalGenerator->GetIncludeFlags(lang));
-    // Add include directory flags.
-    this->LocalGenerator->
-      AppendFlags(flags,this->GetFrameworkFlags().c_str());
-
-    *this->FlagFileStream << lang << "_FLAGS = " << flags << "\n\n";
-    *this->FlagFileStream << lang << "_DEFINES = " << defines << "\n\n";
+    *this->FlagFileStream << *l << "_FLAGS = " << this->GetFlags(*l) << "\n\n";
+    *this->FlagFileStream << *l << "_DEFINES = " << this->GetDefines(*l) <<
+      "\n\n";
     }
 
   // Add target-specific flags.
   if(this->Target->GetProperty("COMPILE_FLAGS"))
     {
-    std::string flags;    
+    std::string flags;
     this->LocalGenerator->AppendFlags
       (flags, this->Target->GetProperty("COMPILE_FLAGS"));
     *this->FlagFileStream << "# TARGET_FLAGS = " << flags << "\n\n";
@@ -635,6 +645,9 @@ cmMakefileTargetGenerator
   vars.Flags = flags.c_str();
   vars.Defines = defines.c_str();
 
+  bool lang_is_c_or_cxx = ((strcmp(lang, "C") == 0) ||
+                           (strcmp(lang, "CXX") == 0));
+
   // Construct the compile rules.
   {
   std::string compileRuleVar = "CMAKE_";
@@ -645,6 +658,22 @@ cmMakefileTargetGenerator
   std::vector<std::string> compileCommands;
   cmSystemTools::ExpandListArgument(compileRule, compileCommands);
 
+  if (lang_is_c_or_cxx && compileCommands.size() == 1)
+    {
+    std::string compileCommand = compileCommands[0];
+    this->LocalGenerator->ExpandRuleVariables(compileCommand, vars);
+    std::string workingDirectory =
+      this->LocalGenerator->Convert(
+        this->Makefile->GetStartOutputDirectory(), cmLocalGenerator::FULL);
+    compileCommand.replace(compileCommand.find(langFlags),
+                           langFlags.size(), this->GetFlags(lang));
+    std::string langDefines = std::string("$(") + lang + "_DEFINES)";
+    compileCommand.replace(compileCommand.find(langDefines),
+                           langDefines.size(), this->GetDefines(lang));
+    this->GlobalGenerator->AddCXXCompileCommand(
+      source.GetFullPath(), workingDirectory, compileCommand);
+    }
+
   // Expand placeholders in the commands.
   for(std::vector<std::string>::iterator i = compileCommands.begin();
       i != compileCommands.end(); ++i)
@@ -657,6 +686,7 @@ cmMakefileTargetGenerator
     (compileCommands,
      this->Makefile->GetStartOutputDirectory(),
      cmLocalGenerator::HOME_OUTPUT);
+
   commands.insert(commands.end(),
                   compileCommands.begin(), compileCommands.end());
   }
@@ -685,8 +715,6 @@ cmMakefileTargetGenerator
       }
     }
 
-  bool lang_is_c_or_cxx = ((strcmp(lang, "C") == 0) ||
-                           (strcmp(lang, "CXX") == 0));
   bool do_preprocess_rules = lang_is_c_or_cxx &&
     this->LocalGenerator->GetCreatePreprocessedSourceRules();
   bool do_assembly_rules = lang_is_c_or_cxx &&
diff --git a/Source/cmMakefileTargetGenerator.h b/Source/cmMakefileTargetGenerator.h
index c9aede2..12dbb25 100644
--- a/Source/cmMakefileTargetGenerator.h
+++ b/Source/cmMakefileTargetGenerator.h
@@ -212,6 +212,9 @@ protected:
   std::string MacContentDirectory;
   std::set<cmStdString> MacContentFolders;
 
+  std::string GetFlags(const std::string &l);
+  std::string GetDefines(const std::string &l);
+
   // Target-wide Fortran module output directory.
   bool FortranModuleDirectoryComputed;
   std::string FortranModuleDirectory;
_______________________________________________
cmake-developers mailing list
cmake-developers@cmake.org
http://public.kitware.com/cgi-bin/mailman/listinfo/cmake-developers

Reply via email to