Merge topic 'output-compile-lines'

cdc2b41 Fix CompileCommandOutput test build on Windows
7039d1f Fix CompileCommandOutput test for Make tools not supporting spaces
4268e3d run_compile_commands: Cast istream::get() result to char
c45c60b run_compile_commands: Avoid extra stl vector conversion
7c5be51 run_compile_commands: Avoid shadow in std::map<>::at workaround
169bb05 Provide std::map<>::at for use in run_compile_commands
4e2185c Make std::map usage more portable in language=>flags/defines maps
a7e7a04 Fix run_compile_commands build on Apple GCC 3.3
c9174c0 Fix signed/unsigned comparison in EscapeJSON
8346a28 Only offer the compile command output feature on unix systems
0e6b05f Adds a test for the compile command line output.
5674844 make compile command output optional
fe07b05 implement cxx command output
65c0c24 cache flags and defines
3f064ef refactor flags and defines
This commit is contained in:
Brad King 2011-05-24 14:48:14 -04:00 committed by CMake Topic Stage
commit e51bbc14f6
17 changed files with 371 additions and 49 deletions

View File

@ -50,6 +50,12 @@ IF(CMAKE_GENERATOR MATCHES "Makefiles")
IF(DEFINED CMAKE_RULE_MESSAGES) IF(DEFINED CMAKE_RULE_MESSAGES)
SET_PROPERTY(GLOBAL PROPERTY RULE_MESSAGES ${CMAKE_RULE_MESSAGES}) SET_PROPERTY(GLOBAL PROPERTY RULE_MESSAGES ${CMAKE_RULE_MESSAGES})
ENDIF(DEFINED CMAKE_RULE_MESSAGES) ENDIF(DEFINED CMAKE_RULE_MESSAGES)
IF(CMAKE_GENERATOR MATCHES "Unix Makefiles")
SET(CMAKE_EXPORT_COMPILE_COMMANDS OFF CACHE BOOL
"Enable/Disable output of compile commands during generation."
)
MARK_AS_ADVANCED(CMAKE_EXPORT_COMPILE_COMMANDS)
ENDIF(CMAKE_GENERATOR MATCHES "Unix Makefiles")
ENDIF(CMAKE_GENERATOR MATCHES "Makefiles") ENDIF(CMAKE_GENERATOR MATCHES "Makefiles")

View File

@ -30,6 +30,7 @@ cmGlobalUnixMakefileGenerator3::cmGlobalUnixMakefileGenerator3()
#else #else
this->UseLinkScript = true; this->UseLinkScript = true;
#endif #endif
this->CommandDatabase = NULL;
} }
void cmGlobalUnixMakefileGenerator3 void cmGlobalUnixMakefileGenerator3
@ -138,6 +139,17 @@ void cmGlobalUnixMakefileGenerator3
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
std::string EscapeJSON(const std::string& s) {
std::string result;
for (std::string::size_type i = 0; i < s.size(); ++i) {
if (s[i] == '"' || s[i] == '\\') {
result += '\\';
}
result += s[i];
}
return result;
}
void cmGlobalUnixMakefileGenerator3::Generate() void cmGlobalUnixMakefileGenerator3::Generate()
{ {
// first do superclass method // first do superclass method
@ -179,6 +191,35 @@ void cmGlobalUnixMakefileGenerator3::Generate()
// write the main makefile // write the main makefile
this->WriteMainMakefile2(); this->WriteMainMakefile2();
this->WriteMainCMakefile(); 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())
+ "/compile_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() void cmGlobalUnixMakefileGenerator3::WriteMainMakefile2()

View File

@ -112,6 +112,10 @@ public:
/** Record per-target progress information. */ /** Record per-target progress information. */
void RecordTargetProgress(cmMakefileTargetGenerator* tg); void RecordTargetProgress(cmMakefileTargetGenerator* tg);
void AddCXXCompileCommand(const std::string &sourceFile,
const std::string &workingDirectory,
const std::string &compileCommand);
protected: protected:
void WriteMainMakefile2(); void WriteMainMakefile2();
void WriteMainCMakefile(); void WriteMainCMakefile();
@ -176,6 +180,8 @@ protected:
size_t CountProgressMarksInTarget(cmTarget* target, size_t CountProgressMarksInTarget(cmTarget* target,
std::set<cmTarget*>& emitted); std::set<cmTarget*>& emitted);
size_t CountProgressMarksInAll(cmLocalUnixMakefileGenerator3* lg); size_t CountProgressMarksInAll(cmLocalUnixMakefileGenerator3* lg);
cmGeneratedFileStream *CommandDatabase;
}; };
#endif #endif

View File

@ -249,32 +249,56 @@ void cmMakefileTargetGenerator::WriteCommonCodeRules()
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
void cmMakefileTargetGenerator::WriteTargetLanguageFlags() std::string cmMakefileTargetGenerator::GetFlags(const std::string &l)
{ {
// write language flags for target ByLanguageMap::iterator i = this->FlagsByLanguage.find(l);
std::set<cmStdString> languages; if (i == this->FlagsByLanguage.end())
this->Target->GetLanguages(languages);
// put the compiler in the rules.make file so that if it changes
// things rebuild
for(std::set<cmStdString>::const_iterator l = languages.begin();
l != languages.end(); ++l)
{ {
cmStdString compiler = "CMAKE_";
compiler += *l;
compiler += "_COMPILER";
*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 flags;
std::string defines; const char *lang = l.c_str();
bool shared = ((this->Target->GetType() == cmTarget::SHARED_LIBRARY) || bool shared = ((this->Target->GetType() == cmTarget::SHARED_LIBRARY) ||
(this->Target->GetType() == cmTarget::MODULE_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->AddIncludeFlags(flags, lang);
// Append old-style preprocessor definition flags.
this->LocalGenerator->
AppendFlags(flags, this->Makefile->GetDefineFlags());
// Add include directory flags.
this->LocalGenerator->
AppendFlags(flags,this->GetFrameworkFlags().c_str());
ByLanguageMap::value_type entry(l, flags);
i = this->FlagsByLanguage.insert(entry).first;
}
return i->second;
}
std::string cmMakefileTargetGenerator::GetDefines(const std::string &l)
{
ByLanguageMap::iterator i = this->DefinesByLanguage.find(l);
if (i == this->DefinesByLanguage.end())
{
std::string defines;
const char *lang = l.c_str();
// Add the export symbol definition for shared library objects. // Add the export symbol definition for shared library objects.
if(const char* exportMacro = this->Target->GetExportMacro()) if(const char* exportMacro = this->Target->GetExportMacro())
{ {
@ -294,40 +318,41 @@ void cmMakefileTargetGenerator::WriteTargetLanguageFlags()
this->LocalGenerator->AppendDefines this->LocalGenerator->AppendDefines
(defines, this->Target->GetProperty(defPropName.c_str()), lang); (defines, this->Target->GetProperty(defPropName.c_str()), lang);
// Add language feature flags. ByLanguageMap::value_type entry(l, defines);
this->AddFeatureFlags(flags, lang); i = this->DefinesByLanguage.insert(entry).first;
}
return i->second;
}
this->LocalGenerator->AddArchitectureFlags(flags, this->Target, void cmMakefileTargetGenerator::WriteTargetLanguageFlags()
lang, this->ConfigName); {
// write language flags for target
std::set<cmStdString> languages;
this->Target->GetLanguages(languages);
// put the compiler in the rules.make file so that if it changes
// things rebuild
for(std::set<cmStdString>::const_iterator l = languages.begin();
l != languages.end(); ++l)
{
cmStdString compiler = "CMAKE_";
compiler += *l;
compiler += "_COMPILER";
*this->FlagFileStream << "# compile " << l->c_str() << " with " <<
this->Makefile->GetSafeDefinition(compiler.c_str()) << "\n";
}
// Fortran-specific flags computed for this target. for(std::set<cmStdString>::const_iterator l = languages.begin();
if(*l == "Fortran") l != languages.end(); ++l)
{ {
this->AddFortranFlags(flags); *this->FlagFileStream << *l << "_FLAGS = " << this->GetFlags(*l) << "\n\n";
} *this->FlagFileStream << *l << "_DEFINES = " << this->GetDefines(*l) <<
"\n\n";
// Add shared-library flags if needed.
this->LocalGenerator->AddSharedFlags(flags, lang, shared);
// Add include directory flags.
this->AddIncludeFlags(flags, lang);
// Append old-style preprocessor definition flags.
this->LocalGenerator->
AppendFlags(flags, this->Makefile->GetDefineFlags());
// 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";
} }
// Add target-specific flags. // Add target-specific flags.
if(this->Target->GetProperty("COMPILE_FLAGS")) if(this->Target->GetProperty("COMPILE_FLAGS"))
{ {
std::string flags; std::string flags;
this->LocalGenerator->AppendFlags this->LocalGenerator->AppendFlags
(flags, this->Target->GetProperty("COMPILE_FLAGS")); (flags, this->Target->GetProperty("COMPILE_FLAGS"));
*this->FlagFileStream << "# TARGET_FLAGS = " << flags << "\n\n"; *this->FlagFileStream << "# TARGET_FLAGS = " << flags << "\n\n";
@ -641,6 +666,9 @@ cmMakefileTargetGenerator
vars.Flags = flags.c_str(); vars.Flags = flags.c_str();
vars.Defines = defines.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. // Construct the compile rules.
{ {
std::string compileRuleVar = "CMAKE_"; std::string compileRuleVar = "CMAKE_";
@ -651,6 +679,23 @@ cmMakefileTargetGenerator
std::vector<std::string> compileCommands; std::vector<std::string> compileCommands;
cmSystemTools::ExpandListArgument(compileRule, compileCommands); cmSystemTools::ExpandListArgument(compileRule, compileCommands);
if (this->Makefile->IsOn("CMAKE_EXPORT_COMPILE_COMMANDS") &&
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. // Expand placeholders in the commands.
for(std::vector<std::string>::iterator i = compileCommands.begin(); for(std::vector<std::string>::iterator i = compileCommands.begin();
i != compileCommands.end(); ++i) i != compileCommands.end(); ++i)
@ -691,8 +736,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 && bool do_preprocess_rules = lang_is_c_or_cxx &&
this->LocalGenerator->GetCreatePreprocessedSourceRules(); this->LocalGenerator->GetCreatePreprocessedSourceRules();
bool do_assembly_rules = lang_is_c_or_cxx && bool do_assembly_rules = lang_is_c_or_cxx &&

View File

@ -216,6 +216,12 @@ protected:
std::string MacContentDirectory; std::string MacContentDirectory;
std::set<cmStdString> MacContentFolders; std::set<cmStdString> MacContentFolders;
typedef std::map<cmStdString, cmStdString> ByLanguageMap;
std::string GetFlags(const std::string &l);
ByLanguageMap FlagsByLanguage;
std::string GetDefines(const std::string &l);
ByLanguageMap DefinesByLanguage;
// Target-wide Fortran module output directory. // Target-wide Fortran module output directory.
bool FortranModuleDirectoryComputed; bool FortranModuleDirectoryComputed;
std::string FortranModuleDirectory; std::string FortranModuleDirectory;

View File

@ -445,6 +445,13 @@ public:
args.push_back(*arg); args.push_back(*arg);
} }
} }
void Store(std::vector<cmStdString>& args) const
{
for(char** arg = this->ArgV; arg && *arg; ++arg)
{
args.push_back(*arg);
}
}
}; };
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@ -456,6 +463,15 @@ void cmSystemTools::ParseUnixCommandLine(const char* command,
argv.Store(args); argv.Store(args);
} }
//----------------------------------------------------------------------------
void cmSystemTools::ParseUnixCommandLine(const char* command,
std::vector<cmStdString>& args)
{
// Invoke the underlying parser.
cmSystemToolsArgV argv = cmsysSystem_Parse_CommandForUnix(command, 0);
argv.Store(args);
}
std::string cmSystemTools::EscapeWindowsShellArgument(const char* arg, std::string cmSystemTools::EscapeWindowsShellArgument(const char* arg,
int shell_flags) int shell_flags)
{ {

View File

@ -237,6 +237,8 @@ public:
/** Parse arguments out of a unix command line string. */ /** Parse arguments out of a unix command line string. */
static void ParseUnixCommandLine(const char* command, static void ParseUnixCommandLine(const char* command,
std::vector<std::string>& args); std::vector<std::string>& args);
static void ParseUnixCommandLine(const char* command,
std::vector<cmStdString>& args);
/** Compute an escaped version of the given argument for use in a /** Compute an escaped version of the given argument for use in a
windows shell. See kwsys/System.h.in for details. */ windows shell. See kwsys/System.h.in for details. */

View File

@ -30,3 +30,8 @@ endif()
foreach(test ${CMakeLib_TESTS}) foreach(test ${CMakeLib_TESTS})
add_test(CMakeLib.${test} CMakeLibTests ${test}) add_test(CMakeLib.${test} CMakeLibTests ${test})
endforeach() endforeach()
if(TEST_CompileCommandOutput)
add_executable(runcompilecommands run_compile_commands.cxx)
target_link_libraries(runcompilecommands CMakeLib)
endif()

View File

@ -0,0 +1,141 @@
#include "cmSystemTools.h"
class CompileCommandParser {
public:
class CommandType: public std::map<cmStdString, cmStdString>
{
public:
cmStdString const& at(cmStdString const& k) const
{
const_iterator i = this->find(k);
if(i != this->end()) { return i->second; }
static cmStdString emptyString;
return emptyString;
}
};
typedef std::vector<CommandType> TranslationUnitsType;
CompileCommandParser(std::ifstream *input)
{
this->Input = input;
}
void Parse()
{
NextNonWhitespace();
ParseTranslationUnits();
}
const TranslationUnitsType& GetTranslationUnits()
{
return this->TranslationUnits;
}
private:
void ParseTranslationUnits()
{
this->TranslationUnits = TranslationUnitsType();
ExpectOrDie('[', "at start of compile command file");
do
{
ParseTranslationUnit();
this->TranslationUnits.push_back(this->Command);
} while(Expect(','));
ExpectOrDie(']', "at end of array");
}
void ParseTranslationUnit()
{
this->Command = CommandType();
if(!Expect('{')) return;
if(Expect('}')) return;
do
{
ParseString();
std::string name = this->String;
ExpectOrDie(':', "between name and value");
ParseString();
std::string value = this->String;
this->Command[name] = value;
} while(Expect(','));
ExpectOrDie('}', "at end of object");
}
void ParseString()
{
this->String.clear();
if(!Expect('"')) return;
while (!Expect('"'))
{
Expect('\\');
this->String.push_back(C);
Next();
}
}
bool Expect(char c)
{
if(this->C == c)
{
NextNonWhitespace();
return true;
}
return false;
}
void ExpectOrDie(char c, const std::string & message)
{
if (!Expect(c))
ErrorExit(std::string("'") + c + "' expected " + message + ".");
}
void NextNonWhitespace()
{
do { Next(); } while (IsWhitespace());
}
void Next()
{
this->C = char(Input->get());
if (this->Input->bad()) ErrorExit("Unexpected end of file.");
}
void ErrorExit(const std::string &message) {
std::cout << "ERROR: " << message;
exit(1);
}
bool IsWhitespace()
{
return (this->C == ' ' || this->C == '\t' ||
this->C == '\n' || this->C == '\r');
}
char C;
TranslationUnitsType TranslationUnits;
CommandType Command;
std::string String;
std::ifstream *Input;
};
int main ()
{
std::ifstream file("compile_commands.json");
CompileCommandParser parser(&file);
parser.Parse();
for(CompileCommandParser::TranslationUnitsType::const_iterator
it = parser.GetTranslationUnits().begin(),
end = parser.GetTranslationUnits().end(); it != end; ++it)
{
std::vector<cmStdString> command;
cmSystemTools::ParseUnixCommandLine(it->at("command").c_str(), command);
if (!cmSystemTools::RunSingleCommand(
command, 0, 0, it->at("directory").c_str()))
{
std::cout << "ERROR: Failed to run command \""
<< command[0] << "\"" << std::endl;
exit(1);
}
}
return 0;
}

View File

@ -11,6 +11,7 @@ MACRO(ADD_TEST_MACRO NAME COMMAND)
--build-generator ${CMAKE_TEST_GENERATOR} --build-generator ${CMAKE_TEST_GENERATOR}
--build-makeprogram ${CMAKE_TEST_MAKEPROGRAM} --build-makeprogram ${CMAKE_TEST_MAKEPROGRAM}
--build-project ${proj} --build-project ${proj}
${${NAME}_EXTRA_OPTIONS}
--test-command ${COMMAND} ${ARGN}) --test-command ${COMMAND} ${ARGN})
LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/${dir}") LIST(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/${dir}")
ENDMACRO(ADD_TEST_MACRO) ENDMACRO(ADD_TEST_MACRO)
@ -39,6 +40,10 @@ CONFIGURE_FILE(${CMake_SOURCE_DIR}/Tests/EnforceConfig.cmake.in
# Testing # Testing
IF(BUILD_TESTING) IF(BUILD_TESTING)
IF("${CMAKE_TEST_GENERATOR}" MATCHES "Unix Makefiles")
SET(TEST_CompileCommandOutput 1)
ENDIF()
ADD_SUBDIRECTORY(CMakeLib) ADD_SUBDIRECTORY(CMakeLib)
# Collect a list of all test build directories. # Collect a list of all test build directories.
@ -2031,6 +2036,13 @@ ${CMake_BINARY_DIR}/bin/cmake -DVERSION=master -P ${CMake_SOURCE_DIR}/Utilities/
ENDIF() ENDIF()
SET_TESTS_PROPERTIES(Contracts.${project} PROPERTIES TIMEOUT ${timeout}) SET_TESTS_PROPERTIES(Contracts.${project} PROPERTIES TIMEOUT ${timeout})
ENDFOREACH() ENDFOREACH()
IF(TEST_CompileCommandOutput)
SET(CompileCommandOutput_EXTRA_OPTIONS
--build-options -DMAKE_SUPPORTS_SPACES=${MAKE_IS_GNU})
ADD_TEST_MACRO(CompileCommandOutput
"${CMake_BINARY_DIR}/Tests/CMakeLib/runcompilecommands")
ENDIF()
ENDIF(BUILD_TESTING) ENDIF(BUILD_TESTING)
SUBDIRS(CMakeTests) SUBDIRS(CMakeTests)

View File

@ -0,0 +1,16 @@
# a simple C only test case
cmake_minimum_required (VERSION 2.6)
project (CompileCommandOutput CXX)
SET(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_DEBUG_POSTFIX "_test_debug_postfix")
IF(MAKE_SUPPORTS_SPACES)
SET(test1_srcs "file with spaces.cxx")
ELSE()
SET(test1_srcs "file_with_underscores.cxx")
ENDIF()
ADD_LIBRARY(test1 STATIC ${test1_srcs})
ADD_LIBRARY(test2 SHARED "../CompileCommandOutput/relative.cxx")
INCLUDE_DIRECTORIES(${CompileCommandOutput_SOURCE_DIR}/../../Source)
ADD_EXECUTABLE(CompileCommandOutput compile_command_output.cxx)
TARGET_LINK_LIBRARIES(CompileCommandOutput test1 test2)

View File

@ -0,0 +1,9 @@
#include "file_with_underscores.h"
#include "relative.h"
int main (int argc, char** argv)
{
file_with_underscores();
relative();
return 0;
}

View File

@ -0,0 +1 @@
#include "file_with_underscores.cxx"

View File

@ -0,0 +1,3 @@
#include "file_with_underscores.h"
void file_with_underscores() {}

View File

@ -0,0 +1 @@
void file_with_underscores();

View File

@ -0,0 +1,3 @@
#include "relative.h"
void relative() {}

View File

@ -0,0 +1,11 @@
#if defined(_WIN32)
# ifdef test2_EXPORTS
# define TEST2_EXPORT __declspec(dllexport)
# else
# define TEST2_EXPORT __declspec(dllimport)
# endif
#else
# define TEST2_EXPORT
#endif
TEST2_EXPORT void relative();