| /*============================================================================ |
| CMake - Cross Platform Makefile Generator |
| Copyright 2004-2009 Kitware, Inc. |
| Copyright 2004 Alexander Neundorf (neundorf@kde.org) |
| |
| 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 "cmGlobalKdevelopGenerator.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/Directory.hxx> |
| |
| //---------------------------------------------------------------------------- |
| void cmGlobalKdevelopGenerator |
| ::GetDocumentation(cmDocumentationEntry& entry, const char*) const |
| { |
| entry.Name = this->GetName(); |
| entry.Brief = "Generates KDevelop 3 project files."; |
| entry.Full = |
| "Project files for KDevelop 3 will be created in the top directory " |
| "and in every subdirectory which features a CMakeLists.txt file " |
| "containing a PROJECT() call. " |
| "If you change the settings using KDevelop cmake will try its best " |
| "to keep your changes when regenerating the project files. " |
| "Additionally a hierarchy of UNIX makefiles is generated into the " |
| "build tree. Any " |
| "standard UNIX-style make program can build the project through the " |
| "default make target. A \"make install\" target is also provided."; |
| } |
| |
| cmGlobalKdevelopGenerator::cmGlobalKdevelopGenerator() |
| :cmExternalMakefileProjectGenerator() |
| { |
| this->SupportedGlobalGenerators.push_back("Unix Makefiles"); |
| #ifdef CMAKE_USE_NINJA |
| this->SupportedGlobalGenerators.push_back("Ninja"); |
| #endif |
| } |
| |
| void cmGlobalKdevelopGenerator::Generate() |
| { |
| // for each sub project in the project create |
| // a kdevelop project |
| for (std::map<cmStdString, std::vector<cmLocalGenerator*> >::const_iterator |
| it = this->GlobalGenerator->GetProjectMap().begin(); |
| it!= this->GlobalGenerator->GetProjectMap().end(); |
| ++it) |
| { |
| cmMakefile* mf = it->second[0]->GetMakefile(); |
| std::string outputDir=mf->GetStartOutputDirectory(); |
| std::string projectDir=mf->GetHomeDirectory(); |
| std::string projectName=mf->GetProjectName(); |
| std::string cmakeFilePattern("CMakeLists.txt;*.cmake;"); |
| std::string fileToOpen; |
| const std::vector<cmLocalGenerator*>& lgs= it->second; |
| // create the project.kdevelop.filelist file |
| if(!this->CreateFilelistFile(lgs, outputDir, projectDir, |
| projectName, cmakeFilePattern, fileToOpen)) |
| { |
| cmSystemTools::Error("Can not create filelist file"); |
| return; |
| } |
| //try to find the name of an executable so we have something to |
| //run from kdevelop for now just pick the first executable found |
| std::string executable; |
| for (std::vector<cmLocalGenerator*>::const_iterator lg=lgs.begin(); |
| lg!=lgs.end(); lg++) |
| { |
| cmMakefile* makefile=(*lg)->GetMakefile(); |
| cmTargets& targets=makefile->GetTargets(); |
| for (cmTargets::iterator ti = targets.begin(); |
| ti != targets.end(); ti++) |
| { |
| if (ti->second.GetType()==cmTarget::EXECUTABLE) |
| { |
| executable = ti->second.GetProperty("LOCATION"); |
| break; |
| } |
| } |
| if (!executable.empty()) |
| { |
| break; |
| } |
| } |
| |
| // now create a project file |
| this->CreateProjectFile(outputDir, projectDir, projectName, |
| executable, cmakeFilePattern, fileToOpen); |
| } |
| } |
| |
| bool cmGlobalKdevelopGenerator |
| ::CreateFilelistFile(const std::vector<cmLocalGenerator*>& lgs, |
| const std::string& outputDir, |
| const std::string& projectDirIn, |
| const std::string& projectname, |
| std::string& cmakeFilePattern, |
| std::string& fileToOpen) |
| { |
| std::string projectDir = projectDirIn + "/"; |
| std::string filename = outputDir+ "/" + projectname +".kdevelop.filelist"; |
| |
| std::set<cmStdString> files; |
| std::string tmp; |
| |
| for (std::vector<cmLocalGenerator*>::const_iterator it=lgs.begin(); |
| it!=lgs.end(); it++) |
| { |
| cmMakefile* makefile=(*it)->GetMakefile(); |
| const std::vector<std::string>& listFiles=makefile->GetListFiles(); |
| for (std::vector<std::string>::const_iterator lt=listFiles.begin(); |
| lt!=listFiles.end(); lt++) |
| { |
| tmp=*lt; |
| cmSystemTools::ReplaceString(tmp, projectDir.c_str(), ""); |
| // make sure the file is part of this source tree |
| if ((tmp[0]!='/') && |
| (strstr(tmp.c_str(), |
| cmake::GetCMakeFilesDirectoryPostSlash())==0)) |
| { |
| files.insert(tmp); |
| tmp=cmSystemTools::GetFilenameName(tmp); |
| //add all files which dont match the default |
| // */CMakeLists.txt;*cmake; to the file pattern |
| if ((tmp!="CMakeLists.txt") |
| && (strstr(tmp.c_str(), ".cmake")==0)) |
| { |
| cmakeFilePattern+=tmp+";"; |
| } |
| } |
| } |
| |
| //get all sources |
| cmTargets& targets=makefile->GetTargets(); |
| for (cmTargets::iterator ti = targets.begin(); |
| ti != targets.end(); ti++) |
| { |
| const std::vector<cmSourceFile*>& sources=ti->second.GetSourceFiles(); |
| for (std::vector<cmSourceFile*>::const_iterator si=sources.begin(); |
| si!=sources.end(); si++) |
| { |
| tmp=(*si)->GetFullPath(); |
| std::string headerBasename=cmSystemTools::GetFilenamePath(tmp); |
| headerBasename+="/"; |
| headerBasename+=cmSystemTools::GetFilenameWithoutExtension(tmp); |
| |
| cmSystemTools::ReplaceString(tmp, projectDir.c_str(), ""); |
| |
| if ((tmp[0]!='/') && |
| (strstr(tmp.c_str(), |
| cmake::GetCMakeFilesDirectoryPostSlash())==0) && |
| (cmSystemTools::GetFilenameExtension(tmp)!=".moc")) |
| { |
| files.insert(tmp); |
| |
| // check if there's a matching header around |
| for(std::vector<std::string>::const_iterator |
| ext = makefile->GetHeaderExtensions().begin(); |
| ext != makefile->GetHeaderExtensions().end(); ++ext) |
| { |
| std::string hname=headerBasename; |
| hname += "."; |
| hname += *ext; |
| if(cmSystemTools::FileExists(hname.c_str())) |
| { |
| cmSystemTools::ReplaceString(hname, projectDir.c_str(), ""); |
| files.insert(hname); |
| break; |
| } |
| } |
| } |
| } |
| for (std::vector<std::string>::const_iterator lt=listFiles.begin(); |
| lt!=listFiles.end(); lt++) |
| { |
| tmp=*lt; |
| cmSystemTools::ReplaceString(tmp, projectDir.c_str(), ""); |
| if ((tmp[0]!='/') && |
| (strstr(tmp.c_str(), |
| cmake::GetCMakeFilesDirectoryPostSlash())==0)) |
| { |
| files.insert(tmp.c_str()); |
| } |
| } |
| } |
| } |
| |
| //check if the output file already exists and read it |
| //insert all files which exist into the set of files |
| std::ifstream oldFilelist(filename.c_str()); |
| if (oldFilelist) |
| { |
| while (cmSystemTools::GetLineFromStream(oldFilelist, tmp)) |
| { |
| if (tmp[0]=='/') |
| { |
| continue; |
| } |
| std::string completePath=projectDir+tmp; |
| if (cmSystemTools::FileExists(completePath.c_str())) |
| { |
| files.insert(tmp); |
| } |
| } |
| oldFilelist.close(); |
| } |
| |
| //now write the new filename |
| cmGeneratedFileStream fout(filename.c_str()); |
| if(!fout) |
| { |
| return false; |
| } |
| |
| fileToOpen=""; |
| for (std::set<cmStdString>::const_iterator it=files.begin(); |
| it!=files.end(); it++) |
| { |
| // get the full path to the file |
| tmp=cmSystemTools::CollapseFullPath(it->c_str(), projectDir.c_str()); |
| // just select the first source file |
| if (fileToOpen.empty()) |
| { |
| std::string ext = cmSystemTools::GetFilenameExtension(tmp); |
| if ((ext==".c") || (ext==".cc") || (ext==".cpp") || (ext==".cxx") |
| || (ext==".C") || (ext==".h") || (ext==".hpp")) |
| { |
| fileToOpen=tmp; |
| } |
| } |
| // make it relative to the project dir |
| cmSystemTools::ReplaceString(tmp, projectDir.c_str(), ""); |
| // only put relative paths |
| if (tmp.size() && tmp[0] != '/') |
| { |
| fout << tmp.c_str() <<"\n"; |
| } |
| } |
| return true; |
| } |
| |
| |
| /* create the project file, if it already exists, merge it with the |
| existing one, otherwise create a new one */ |
| void cmGlobalKdevelopGenerator |
| ::CreateProjectFile(const std::string& outputDir, |
| const std::string& projectDir, |
| const std::string& projectname, |
| const std::string& executable, |
| const std::string& cmakeFilePattern, |
| const std::string& fileToOpen) |
| { |
| this->Blacklist.clear(); |
| |
| std::string filename=outputDir+"/"; |
| filename+=projectname+".kdevelop"; |
| std::string sessionFilename=outputDir+"/"; |
| sessionFilename+=projectname+".kdevses"; |
| |
| if (cmSystemTools::FileExists(filename.c_str())) |
| { |
| this->MergeProjectFiles(outputDir, projectDir, filename, |
| executable, cmakeFilePattern, |
| fileToOpen, sessionFilename); |
| } |
| else |
| { |
| // add all subdirectories which are cmake build directories to the |
| // kdevelop blacklist so they are not monitored for added or removed files |
| // since this is handled by adding files to the cmake files |
| cmsys::Directory d; |
| if (d.Load(projectDir.c_str())) |
| { |
| size_t numf = d.GetNumberOfFiles(); |
| for (unsigned int i = 0; i < numf; i++) |
| { |
| std::string nextFile = d.GetFile(i); |
| if ((nextFile!=".") && (nextFile!="..")) |
| { |
| std::string tmp = projectDir; |
| tmp += "/"; |
| tmp += nextFile; |
| if (cmSystemTools::FileIsDirectory(tmp.c_str())) |
| { |
| tmp += "/CMakeCache.txt"; |
| if ((nextFile == "CMakeFiles") |
| || (cmSystemTools::FileExists(tmp.c_str()))) |
| { |
| this->Blacklist.push_back(nextFile); |
| } |
| } |
| } |
| } |
| } |
| this->CreateNewProjectFile(outputDir, projectDir, filename, |
| executable, cmakeFilePattern, |
| fileToOpen, sessionFilename); |
| } |
| |
| } |
| |
| void cmGlobalKdevelopGenerator |
| ::MergeProjectFiles(const std::string& outputDir, |
| const std::string& projectDir, |
| const std::string& filename, |
| const std::string& executable, |
| const std::string& cmakeFilePattern, |
| const std::string& fileToOpen, |
| const std::string& sessionFilename) |
| { |
| std::ifstream oldProjectFile(filename.c_str()); |
| if (!oldProjectFile) |
| { |
| this->CreateNewProjectFile(outputDir, projectDir, filename, |
| executable, cmakeFilePattern, |
| fileToOpen, sessionFilename); |
| return; |
| } |
| |
| /* Read the existing project file (line by line), copy all lines |
| into the new project file, except the ones which can be reliably |
| set from contents of the CMakeLists.txt */ |
| std::string tmp; |
| std::vector<std::string> lines; |
| while (cmSystemTools::GetLineFromStream(oldProjectFile, tmp)) |
| { |
| lines.push_back(tmp); |
| } |
| oldProjectFile.close(); |
| |
| cmGeneratedFileStream fout(filename.c_str()); |
| if(!fout) |
| { |
| return; |
| } |
| |
| for (std::vector<std::string>::const_iterator it=lines.begin(); |
| it!=lines.end(); it++) |
| { |
| const char* line=(*it).c_str(); |
| // skip these tags as they are always replaced |
| if ((strstr(line, "<projectdirectory>")!=0) |
| || (strstr(line, "<projectmanagement>")!=0) |
| || (strstr(line, "<absoluteprojectpath>")!=0) |
| || (strstr(line, "<filelistdirectory>")!=0) |
| || (strstr(line, "<buildtool>")!=0) |
| || (strstr(line, "<builddir>")!=0)) |
| { |
| continue; |
| } |
| |
| // output the line from the file if it is not one of the above tags |
| fout<<*it<<"\n"; |
| // if this is the <general> tag output the stuff that goes in the |
| // general tag |
| if (strstr(line, "<general>")) |
| { |
| fout<< " <projectmanagement>KDevCustomProject</projectmanagement>\n"; |
| fout<< " <projectdirectory>" <<projectDir.c_str() |
| << "</projectdirectory>\n"; //this one is important |
| fout<<" <absoluteprojectpath>true</absoluteprojectpath>\n"; |
| //and this one |
| } |
| // inside kdevcustomproject the <filelistdirectory> must be put |
| if (strstr(line, "<kdevcustomproject>")) |
| { |
| fout<<" <filelistdirectory>"<<outputDir.c_str() |
| <<"</filelistdirectory>\n"; |
| } |
| // buildtool and builddir go inside <build> |
| if (strstr(line, "<build>")) |
| { |
| fout<<" <buildtool>make</buildtool>\n"; |
| fout<<" <builddir>"<<outputDir.c_str()<<"</builddir>\n"; |
| } |
| } |
| } |
| |
| void cmGlobalKdevelopGenerator |
| ::CreateNewProjectFile(const std::string& outputDir, |
| const std::string& projectDir, |
| const std::string& filename, |
| const std::string& executable, |
| const std::string& cmakeFilePattern, |
| const std::string& fileToOpen, |
| const std::string& sessionFilename) |
| { |
| cmGeneratedFileStream fout(filename.c_str()); |
| if(!fout) |
| { |
| return; |
| } |
| |
| // check for a version control system |
| bool hasSvn = cmSystemTools::FileExists((projectDir + "/.svn").c_str()); |
| bool hasCvs = cmSystemTools::FileExists((projectDir + "/CVS").c_str()); |
| |
| bool enableCxx = (this->GlobalGenerator->GetLanguageEnabled("C") |
| || this->GlobalGenerator->GetLanguageEnabled("CXX")); |
| bool enableFortran = this->GlobalGenerator->GetLanguageEnabled("Fortran"); |
| std::string primaryLanguage = "C++"; |
| if (enableFortran && !enableCxx) |
| { |
| primaryLanguage="Fortran77"; |
| } |
| |
| fout<<"<?xml version = '1.0'?>\n" |
| "<kdevelop>\n" |
| " <general>\n" |
| " <author></author>\n" |
| " <email></email>\n" |
| " <version>$VERSION$</version>\n" |
| " <projectmanagement>KDevCustomProject</projectmanagement>\n" |
| " <primarylanguage>" << primaryLanguage << "</primarylanguage>\n" |
| " <ignoreparts/>\n" |
| " <projectdirectory>" << projectDir.c_str() << |
| "</projectdirectory>\n"; //this one is important |
| fout<<" <absoluteprojectpath>true</absoluteprojectpath>\n"; //and this one |
| |
| // setup additional languages |
| fout<<" <secondaryLanguages>\n"; |
| if (enableFortran && enableCxx) |
| { |
| fout<<" <language>Fortran</language>\n"; |
| } |
| if (enableCxx) |
| { |
| fout<<" <language>C</language>\n"; |
| } |
| fout<<" </secondaryLanguages>\n"; |
| |
| if (hasSvn) |
| { |
| fout << " <versioncontrol>kdevsubversion</versioncontrol>\n"; |
| } |
| else if (hasCvs) |
| { |
| fout << " <versioncontrol>kdevcvsservice</versioncontrol>\n"; |
| } |
| |
| fout<<" </general>\n" |
| " <kdevcustomproject>\n" |
| " <filelistdirectory>" << outputDir.c_str() << |
| "</filelistdirectory>\n" |
| " <run>\n" |
| " <mainprogram>" << executable.c_str() << "</mainprogram>\n" |
| " <directoryradio>custom</directoryradio>\n" |
| " <customdirectory>"<<outputDir.c_str()<<"</customdirectory>\n" |
| " <programargs></programargs>\n" |
| " <terminal>false</terminal>\n" |
| " <autocompile>true</autocompile>\n" |
| " <envvars/>\n" |
| " </run>\n" |
| " <build>\n" |
| " <buildtool>make</buildtool>\n"; //this one is important |
| fout<<" <builddir>"<<outputDir.c_str()<<"</builddir>\n"; //and this one |
| fout<<" </build>\n" |
| " <make>\n" |
| " <abortonerror>false</abortonerror>\n" |
| " <numberofjobs>1</numberofjobs>\n" |
| " <dontact>false</dontact>\n" |
| " <makebin>" << this->GlobalGenerator->GetLocalGenerators()[0]-> |
| GetMakefile()->GetRequiredDefinition("CMAKE_BUILD_TOOL") |
| << " </makebin>\n" |
| " <selectedenvironment>default</selectedenvironment>\n" |
| " <environments>\n" |
| " <default>\n" |
| " <envvar value=\"1\" name=\"VERBOSE\" />\n" |
| " <envvar value=\"1\" name=\"CMAKE_NO_VERBOSE\" />\n" |
| " </default>\n" |
| " </environments>\n" |
| " </make>\n"; |
| |
| fout<<" <blacklist>\n"; |
| for(std::vector<std::string>::const_iterator dirIt=this->Blacklist.begin(); |
| dirIt != this->Blacklist.end(); |
| ++dirIt) |
| { |
| fout<<" <path>" << dirIt->c_str() << "</path>\n"; |
| } |
| fout<<" </blacklist>\n"; |
| |
| fout<<" </kdevcustomproject>\n" |
| " <kdevfilecreate>\n" |
| " <filetypes/>\n" |
| " <useglobaltypes>\n" |
| " <type ext=\"ui\" />\n" |
| " <type ext=\"cpp\" />\n" |
| " <type ext=\"h\" />\n" |
| " </useglobaltypes>\n" |
| " </kdevfilecreate>\n" |
| " <kdevdoctreeview>\n" |
| " <projectdoc>\n" |
| " <userdocDir>html/</userdocDir>\n" |
| " <apidocDir>html/</apidocDir>\n" |
| " </projectdoc>\n" |
| " <ignoreqt_xml/>\n" |
| " <ignoredoxygen/>\n" |
| " <ignorekdocs/>\n" |
| " <ignoretocs/>\n" |
| " <ignoredevhelp/>\n" |
| " </kdevdoctreeview>\n"; |
| |
| if (enableCxx) |
| { |
| fout<<" <cppsupportpart>\n" |
| " <filetemplates>\n" |
| " <interfacesuffix>.h</interfacesuffix>\n" |
| " <implementationsuffix>.cpp</implementationsuffix>\n" |
| " </filetemplates>\n" |
| " </cppsupportpart>\n" |
| " <kdevcppsupport>\n" |
| " <codecompletion>\n" |
| " <includeGlobalFunctions>true</includeGlobalFunctions>\n" |
| " <includeTypes>true</includeTypes>\n" |
| " <includeEnums>true</includeEnums>\n" |
| " <includeTypedefs>false</includeTypedefs>\n" |
| " <automaticCodeCompletion>true</automaticCodeCompletion>\n" |
| " <automaticArgumentsHint>true</automaticArgumentsHint>\n" |
| " <automaticHeaderCompletion>true</automaticHeaderCompletion>\n" |
| " <codeCompletionDelay>250</codeCompletionDelay>\n" |
| " <argumentsHintDelay>400</argumentsHintDelay>\n" |
| " <headerCompletionDelay>250</headerCompletionDelay>\n" |
| " </codecompletion>\n" |
| " <references/>\n" |
| " </kdevcppsupport>\n"; |
| } |
| |
| if (enableFortran) |
| { |
| fout<<" <kdevfortransupport>\n" |
| " <ftnchek>\n" |
| " <division>false</division>\n" |
| " <extern>false</extern>\n" |
| " <declare>false</declare>\n" |
| " <pure>false</pure>\n" |
| " <argumentsall>false</argumentsall>\n" |
| " <commonall>false</commonall>\n" |
| " <truncationall>false</truncationall>\n" |
| " <usageall>false</usageall>\n" |
| " <f77all>false</f77all>\n" |
| " <portabilityall>false</portabilityall>\n" |
| " <argumentsonly/>\n" |
| " <commononly/>\n" |
| " <truncationonly/>\n" |
| " <usageonly/>\n" |
| " <f77only/>\n" |
| " <portabilityonly/>\n" |
| " </ftnchek>\n" |
| " </kdevfortransupport>\n"; |
| } |
| |
| // set up file groups. maybe this can be used with the CMake SOURCE_GROUP() |
| // command |
| fout<<" <kdevfileview>\n" |
| " <groups>\n" |
| " <group pattern=\"" << cmakeFilePattern.c_str() << |
| "\" name=\"CMake\" />\n"; |
| |
| if (enableCxx) |
| { |
| fout<<" <group pattern=\"*.h;*.hxx;*.hpp\" name=\"Header\" />\n" |
| " <group pattern=\"*.c\" name=\"C Sources\" />\n" |
| " <group pattern=\"*.cpp;*.C;*.cxx;*.cc\" name=\"C++ Sources\"" |
| "/>\n"; |
| } |
| |
| if (enableFortran) |
| { |
| fout<<" <group pattern=\"*.f;*.F;*.f77;*.F77;*.f90;*.F90;*.for;*.f95;" |
| "*.F95\" name=\"Fortran Sources\" />\n"; |
| } |
| |
| fout<<" <group pattern=\"*.ui\" name=\"Qt Designer files\" />\n" |
| " <hidenonprojectfiles>true</hidenonprojectfiles>\n" |
| " </groups>\n" |
| " <tree>\n" |
| " <hidepatterns>*.o,*.lo,CVS,*~,cmake*</hidepatterns>\n" |
| " <hidenonprojectfiles>true</hidenonprojectfiles>\n" |
| " </tree>\n" |
| " </kdevfileview>\n" |
| "</kdevelop>\n"; |
| |
| if (sessionFilename.empty()) |
| { |
| return; |
| } |
| |
| // and a session file, so that kdevelop opens a file if it opens the |
| // project the first time |
| cmGeneratedFileStream devses(sessionFilename.c_str()); |
| if(!devses) |
| { |
| return; |
| } |
| devses<<"<?xml version = '1.0' encoding = \'UTF-8\'?>\n" |
| "<!DOCTYPE KDevPrjSession>\n" |
| "<KDevPrjSession>\n" |
| " <DocsAndViews NumberOfDocuments=\"1\" >\n" |
| " <Doc0 NumberOfViews=\"1\" URL=\"file://" << fileToOpen.c_str() << |
| "\" >\n" |
| " <View0 line=\"0\" Type=\"Source\" />\n" |
| " </Doc0>\n" |
| " </DocsAndViews>\n" |
| "</KDevPrjSession>\n"; |
| } |
| |