/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2004-2009 Kitware, Inc.
Copyright 2004 Alexander Neundorf (neundorf@kde.org)
Copyright 2013 Eran Ifrah (eran.ifrah@gmail.com)
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#include "cmExtraCodeLiteGenerator.h"
#include "cmGlobalUnixMakefileGenerator3.h"
#include "cmLocalUnixMakefileGenerator3.h"
#include "cmMakefile.h"
#include "cmake.h"
#include "cmSourceFile.h"
#include "cmGeneratedFileStream.h"
#include "cmSystemTools.h"
#include <cmsys/SystemTools.hxx>
#include <cmsys/SystemInformation.hxx>
#include <cmsys/Directory.hxx>
#include "cmStandardIncludes.h"
#include "cmXMLSafe.h"
//----------------------------------------------------------------------------
void cmExtraCodeLiteGenerator::GetDocumentation(cmDocumentationEntry& entry,
const std::string&) const
{
entry.Name = this->GetName();
entry.Brief = "Generates CodeLite project files.";
}
cmExtraCodeLiteGenerator::cmExtraCodeLiteGenerator()
: cmExternalMakefileProjectGenerator()
, ConfigName("NoConfig")
, CpuCount(2)
{
#if defined(_WIN32)
this->SupportedGlobalGenerators.push_back("MinGW Makefiles");
this->SupportedGlobalGenerators.push_back("NMake Makefiles");
#endif
this->SupportedGlobalGenerators.push_back("Ninja");
this->SupportedGlobalGenerators.push_back("Unix Makefiles");
}
void cmExtraCodeLiteGenerator::Generate()
{
// Hold root tree information for creating the workspace
std::string workspaceProjectName;
std::string workspaceOutputDir;
std::string workspaceFileName;
std::string workspaceSourcePath;
std::string lprjdebug;
cmGeneratedFileStream fout;
// loop projects and locate the root project.
// and extract the information for creating the worspace
for (std::map<std::string, std::vector<cmLocalGenerator*> >::const_iterator
it = this->GlobalGenerator->GetProjectMap().begin();
it!= this->GlobalGenerator->GetProjectMap().end();
++it)
{
const cmMakefile* mf =it->second[0]->GetMakefile();
this->ConfigName = GetConfigurationName( mf );
if (strcmp(it->second[0]->GetCurrentBinaryDirectory(),
it->second[0]->GetBinaryDirectory()) == 0)
{
workspaceOutputDir = it->second[0]->GetCurrentBinaryDirectory();
workspaceProjectName = it->second[0]->GetProjectName();
workspaceSourcePath = it->second[0]->GetSourceDirectory();
workspaceFileName = workspaceOutputDir+"/";
workspaceFileName += workspaceProjectName + ".workspace";
this->WorkspacePath = it->second[0]->GetCurrentBinaryDirectory();;
fout.Open(workspaceFileName.c_str(), false, false);
fout << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<CodeLite_Workspace Name=\"" << workspaceProjectName << "\" >\n";
}
}
// for each sub project in the workspace create a codelite project
for (std::map<std::string, std::vector<cmLocalGenerator*> >::const_iterator
it = this->GlobalGenerator->GetProjectMap().begin();
it!= this->GlobalGenerator->GetProjectMap().end();
++it)
{
// retrive project information
std::string outputDir = it->second[0]->GetCurrentBinaryDirectory();
std::string projectName = it->second[0]->GetProjectName();
std::string filename = outputDir + "/" + projectName + ".project";
// Make the project file relative to the workspace
filename = cmSystemTools::RelativePath(this->WorkspacePath.c_str(),
filename.c_str());
// create a project file
this->CreateProjectFile(it->second);
fout << " <Project Name=\"" << projectName << "\" Path=\""
<< filename << "\" Active=\"No\"/>\n";
lprjdebug += "<Project Name=\"" + projectName
+ "\" ConfigName=\"" + this->ConfigName + "\"/>\n";
}
fout << " <BuildMatrix>\n"
" <WorkspaceConfiguration Name=\""
<< this->ConfigName << "\" Selected=\"yes\">\n"
" " << lprjdebug << ""
" </WorkspaceConfiguration>\n"
" </BuildMatrix>\n"
"</CodeLite_Workspace>\n";
}
/* create the project file */
void cmExtraCodeLiteGenerator::CreateProjectFile(
const std::vector<cmLocalGenerator*>& lgs)
{
std::string outputDir = lgs[0]->GetCurrentBinaryDirectory();
std::string projectName = lgs[0]->GetProjectName();
std::string filename = outputDir + "/";
filename += projectName + ".project";
this->CreateNewProjectFile(lgs, filename);
}
void cmExtraCodeLiteGenerator
::CreateNewProjectFile(const std::vector<cmLocalGenerator*>& lgs,
const std::string& filename)
{
const cmMakefile* mf=lgs[0]->GetMakefile();
cmGeneratedFileStream fout(filename.c_str());
if(!fout)
{
return;
}
////////////////////////////////////
fout << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<CodeLite_Project Name=\"" << lgs[0]->GetProjectName()
<< "\" InternalType=\"\">\n";
// Collect all used source files in the project
// Sort them into two containers, one for C/C++ implementation files
// which may have an acompanying header, one for all other files
std::string projectType;
std::vector<std::string> srcExts =
this->GlobalGenerator->GetCMakeInstance()->GetSourceExtensions();
std::vector<std::string> headerExts =
this->GlobalGenerator->GetCMakeInstance()->GetHeaderExtensions();
std::map<std::string, cmSourceFile*> cFiles;
std::set<std::string> otherFiles;
for (std::vector<cmLocalGenerator*>::const_iterator lg=lgs.begin();
lg!=lgs.end(); lg++)
{
cmMakefile* makefile=(*lg)->GetMakefile();
std::vector<cmGeneratorTarget*> targets=(*lg)->GetGeneratorTargets();
for (std::vector<cmGeneratorTarget*>::iterator ti = targets.begin();
ti != targets.end(); ti++)
{
switch((*ti)->GetType())
{
case cmState::EXECUTABLE:
{
projectType = "Executable";
}
break;
case cmState::STATIC_LIBRARY:
{
projectType = "Static Library";
}
break;
case cmState::SHARED_LIBRARY:
{
projectType = "Dynamic Library";
}
break;
case cmState::MODULE_LIBRARY:
{
projectType = "Dynamic Library";
}
break;
default: // intended fallthrough
break;
}
switch((*ti)->GetType())
{
case cmState::EXECUTABLE:
case cmState::STATIC_LIBRARY:
case cmState::SHARED_LIBRARY:
case cmState::MODULE_LIBRARY:
{
std::vector<cmSourceFile*> sources;
cmGeneratorTarget* gt = *ti;
gt->GetSourceFiles(sources,
makefile->GetSafeDefinition("CMAKE_BUILD_TYPE"));
for (std::vector<cmSourceFile*>::const_iterator si=sources.begin();
si!=sources.end(); si++)
{
// check whether it is a C/C++ implementation file
bool isCFile = false;
std::string lang = (*si)->GetLanguage();
if (lang == "C" || lang == "CXX")
{
std::string srcext = (*si)->GetExtension();
for(std::vector<std::string>::const_iterator
ext = srcExts.begin(); ext != srcExts.end(); ++ext)
{
if (srcext == *ext)
{
isCFile = true;
break;
}
}
}
// then put it accordingly into one of the two containers
if (isCFile)
{
cFiles[(*si)->GetFullPath()] = *si ;
}
else
{
otherFiles.insert((*si)->GetFullPath());
}
}
}
default: // intended fallthrough
break;
}
}
}
// The following loop tries to add header files matching to implementation
// files to the project. It does that by iterating over all source files,
// replacing the file name extension with ".h" and checks whether such a
// file exists. If it does, it is inserted into the map of files.
// A very similar version of that code exists also in the kdevelop
// project generator.
for (std::map<std::string, cmSourceFile*>::const_iterator
sit=cFiles.begin();
sit!=cFiles.end();
++sit)
{
std::string headerBasename=cmSystemTools::GetFilenamePath(sit->first);
headerBasename+="/";
headerBasename+=cmSystemTools::GetFilenameWithoutExtension(sit->first);
// check if there's a matching header around
for(std::vector<std::string>::const_iterator
ext = headerExts.begin();
ext != headerExts.end();
++ext)
{
std::string hname=headerBasename;
hname += ".";
hname += *ext;
// if it's already in the set, don't check if it exists on disk
std::set<std::string>::const_iterator headerIt=otherFiles.find(hname);
if (headerIt != otherFiles.end())
{
break;
}
if(cmSystemTools::FileExists(hname.c_str()))
{
otherFiles.insert(hname);
break;
}
}
}
// Get the project path ( we need it later to convert files to
// their relative path)
std::string projectPath = cmSystemTools::GetFilenamePath(filename);
// Create 2 virtual folders: src and include
// and place all the implementation files into the src
// folder, the rest goes to the include folder
fout<< " <VirtualDirectory Name=\"src\">\n";
// insert all source files in the codelite project
// first the C/C++ implementation files, then all others
for (std::map<std::string, cmSourceFile*>::const_iterator
sit=cFiles.begin();
sit!=cFiles.end();
++sit)
{
std::string relativePath =
cmSystemTools::RelativePath(projectPath.c_str(), sit->first.c_str());
fout<< " <File Name=\"" << relativePath << "\"/>\n";
}
fout<< " </VirtualDirectory>\n";
fout<< " <VirtualDirectory Name=\"include\">\n";
for (std::set<std::string>::const_iterator
sit=otherFiles.begin();
sit!=otherFiles.end();
++sit)
{
std::string relativePath =
cmSystemTools::RelativePath(projectPath.c_str(), sit->c_str());
fout << " <File Name=\"" << relativePath << "\"/>\n";
}
fout << " </VirtualDirectory>\n";
// Get the number of CPUs. We use this information for the make -jN
// command
cmsys::SystemInformation info;
info.RunCPUCheck();
this->CpuCount = info.GetNumberOfLogicalCPU() *
info.GetNumberOfPhysicalCPU();
std::string cleanCommand = GetCleanCommand(mf);
std::string buildCommand = GetBuildCommand(mf);
std::string rebuildCommand = GetRebuildCommand(mf);
std::string singleFileCommand = GetSingleFileBuildCommand(mf);
std::string codeliteCompilerName = this->GetCodeLiteCompilerName(mf);
fout << "\n"
" <Settings Type=\"" << projectType << "\">\n"
" <Configuration Name=\"" << this->ConfigName << "\" CompilerType=\""
<< codeliteCompilerName << "\" DebuggerType=\"GNU gdb debugger\" "
"Type=\""
<< projectType << "\" BuildCmpWithGlobalSettings=\"append\" "
"BuildLnkWithGlobalSettings=\"append\" "
"BuildResWithGlobalSettings=\"append\">\n"
" <Compiler Options=\"-g\" "
"Required=\"yes\" PreCompiledHeader=\"\">\n"
" <IncludePath Value=\".\"/>\n"
" </Compiler>\n"
" <Linker Options=\"\" Required=\"yes\"/>\n"
" <ResourceCompiler Options=\"\" Required=\"no\"/>\n"
" <General OutputFile=\"$(IntermediateDirectory)/$(ProjectName)\" "
"IntermediateDirectory=\"./\" Command=\"./$(ProjectName)\" "
"CommandArguments=\"\" WorkingDirectory=\"$(IntermediateDirectory)\" "
"PauseExecWhenProcTerminates=\"yes\"/>\n"
" <Debugger IsRemote=\"no\" RemoteHostName=\"\" "
"RemoteHostPort=\"\" DebuggerPath=\"\">\n"
" <PostConnectCommands/>\n"
" <StartupCommands/>\n"
" </Debugger>\n"
" <PreBuild/>\n"
" <PostBuild/>\n"
" <CustomBuild Enabled=\"yes\">\n"
" <RebuildCommand>" << rebuildCommand << "</RebuildCommand>\n"
" <CleanCommand>" << cleanCommand << "</CleanCommand>\n"
" <BuildCommand>" << buildCommand << "</BuildCommand>\n"
" <SingleFileCommand>" << singleFileCommand
<< "</SingleFileCommand>\n"
" <PreprocessFileCommand/>\n"
" <WorkingDirectory>$(WorkspacePath)</WorkingDirectory>\n"
" </CustomBuild>\n"
" <AdditionalRules>\n"
" <CustomPostBuild/>\n"
" <CustomPreBuild/>\n"
" </AdditionalRules>\n"
" </Configuration>\n"
" <GlobalSettings>\n"
" <Compiler Options=\"\">\n"
" <IncludePath Value=\".\"/>\n"
" </Compiler>\n"
" <Linker Options=\"\">\n"
" <LibraryPath Value=\".\"/>\n"
" </Linker>\n"
" <ResourceCompiler Options=\"\"/>\n"
" </GlobalSettings>\n"
" </Settings>\n"
"</CodeLite_Project>\n";
}
std::string
cmExtraCodeLiteGenerator::GetCodeLiteCompilerName(const cmMakefile* mf) const
{
// figure out which language to use
// for now care only for C and C++
std::string compilerIdVar = "CMAKE_CXX_COMPILER_ID";
if (this->GlobalGenerator->GetLanguageEnabled("CXX") == false)
{
compilerIdVar = "CMAKE_C_COMPILER_ID";
}
std::string compilerId = mf->GetSafeDefinition(compilerIdVar);
std::string compiler = "gnu g++"; // default to g++
// Since we need the compiler for parsing purposes only
// it does not matter if we use clang or clang++, same as
// "gnu gcc" vs "gnu g++"
if (compilerId == "MSVC")
{
compiler = "VC++";
}
else if (compilerId == "Clang")
{
compiler = "clang++";
}
else if (compilerId == "GNU")
{
compiler = "gnu g++";
}
return compiler;
}
std::string
cmExtraCodeLiteGenerator::GetConfigurationName(const cmMakefile* mf) const
{
std::string confName = mf->GetSafeDefinition("CMAKE_BUILD_TYPE");
// Trim the configuration name from whitespaces (left and right)
confName.erase(0, confName.find_first_not_of(" \t\r\v\n"));
confName.erase(confName.find_last_not_of(" \t\r\v\n")+1);
if ( confName.empty() )
{
confName = "NoConfig";
}
return confName;
}
std::string
cmExtraCodeLiteGenerator::GetBuildCommand(const cmMakefile* mf) const
{
std::string generator = mf->GetSafeDefinition("CMAKE_GENERATOR");
std::string make = mf->GetRequiredDefinition("CMAKE_MAKE_PROGRAM");
std::string buildCommand = make; // Default
if ( generator == "NMake Makefiles" ||
generator == "Ninja" )
{
buildCommand = make;
}
else if ( generator == "MinGW Makefiles" ||
generator == "Unix Makefiles" )
{
std::ostringstream ss;
ss << make << " -j " << this->CpuCount;
buildCommand = ss.str();
}
return buildCommand;
}
std::string
cmExtraCodeLiteGenerator::GetCleanCommand(const cmMakefile* mf) const
{
return GetBuildCommand(mf) + " clean";
}
std::string
cmExtraCodeLiteGenerator::GetRebuildCommand(const cmMakefile* mf) const
{
return GetCleanCommand(mf) + cmXMLSafe(" && ").str() + GetBuildCommand(mf);
}
std::string
cmExtraCodeLiteGenerator::GetSingleFileBuildCommand
(const cmMakefile* mf) const
{
std::string buildCommand;
std::string make = mf->GetRequiredDefinition("CMAKE_MAKE_PROGRAM");
std::string generator = mf->GetSafeDefinition("CMAKE_GENERATOR");
if ( generator == "Unix Makefiles" || generator == "MinGW Makefiles" )
{
std::ostringstream ss;
ss << make << " -f$(ProjectPath)/Makefile $(CurrentFileName).cpp.o";
buildCommand = ss.str();
}
return buildCommand;
}