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