/*============================================================================ 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"); } 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"; }