/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmGlobalKdevelopGenerator.h" #include "cmGeneratedFileStream.h" #include "cmGeneratorTarget.h" #include "cmGlobalGenerator.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" #include "cmSourceFile.h" #include "cmState.h" #include "cmSystemTools.h" #include "cmTarget.h" #include "cmXMLWriter.h" #include "cmake.h" #include #include #include #include #include #include cmGlobalKdevelopGenerator::cmGlobalKdevelopGenerator() : cmExternalMakefileProjectGenerator() { } cmExternalMakefileProjectGeneratorFactory* cmGlobalKdevelopGenerator::GetFactory() { static cmExternalMakefileProjectGeneratorSimpleFactory< cmGlobalKdevelopGenerator> factory("KDevelop3", "Generates KDevelop 3 project files."); if (factory.GetSupportedGlobalGenerators().empty()) { factory.AddSupportedGlobalGenerator("Unix Makefiles"); #ifdef CMAKE_USE_NINJA factory.AddSupportedGlobalGenerator("Ninja"); #endif factory.Aliases.push_back("KDevelop3"); } return &factory; } void cmGlobalKdevelopGenerator::Generate() { // for each sub project in the project create // a kdevelop project for (std::map >::const_iterator it = this->GlobalGenerator->GetProjectMap().begin(); it != this->GlobalGenerator->GetProjectMap().end(); ++it) { std::string outputDir = it->second[0]->GetCurrentBinaryDirectory(); std::string projectDir = it->second[0]->GetSourceDirectory(); std::string projectName = it->second[0]->GetProjectName(); std::string cmakeFilePattern("CMakeLists.txt;*.cmake;"); std::string fileToOpen; const std::vector& 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::const_iterator lg = lgs.begin(); lg != lgs.end(); lg++) { std::vector const& targets = (*lg)->GetGeneratorTargets(); for (std::vector::const_iterator ti = targets.begin(); ti != targets.end(); ti++) { if ((*ti)->GetType() == cmState::EXECUTABLE) { executable = (*ti)->GetLocation(""); break; } } if (!executable.empty()) { break; } } // now create a project file this->CreateProjectFile(outputDir, projectDir, projectName, executable, cmakeFilePattern, fileToOpen); } } bool cmGlobalKdevelopGenerator::CreateFilelistFile( const std::vector& 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 files; std::string tmp; std::vector hdrExts = this->GlobalGenerator->GetCMakeInstance()->GetHeaderExtensions(); for (std::vector::const_iterator it = lgs.begin(); it != lgs.end(); it++) { cmMakefile* makefile = (*it)->GetMakefile(); const std::vector& listFiles = makefile->GetListFiles(); for (std::vector::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()) == CM_NULLPTR)) { 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") == CM_NULLPTR)) { cmakeFilePattern += tmp + ";"; } } } // get all sources std::vector targets = (*it)->GetGeneratorTargets(); for (std::vector::iterator ti = targets.begin(); ti != targets.end(); ti++) { std::vector sources; cmGeneratorTarget* gt = *ti; gt->GetSourceFiles(sources, gt->Target->GetMakefile()->GetSafeDefinition( "CMAKE_BUILD_TYPE")); for (std::vector::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()) == CM_NULLPTR) && (cmSystemTools::GetFilenameExtension(tmp) != ".moc")) { files.insert(tmp); // check if there's a matching header around for (std::vector::const_iterator ext = hdrExts.begin(); ext != hdrExts.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::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()) == CM_NULLPTR)) { files.insert(tmp); } } } } // check if the output file already exists and read it // insert all files which exist into the set of files cmsys::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::const_iterator it = files.begin(); it != files.end(); it++) { // get the full path to the file tmp = cmSystemTools::CollapseFullPath(*it, 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.empty() && tmp[0] != '/') { fout << tmp << "\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)) { 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)) { 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) { cmsys::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 lines; while (cmSystemTools::GetLineFromStream(oldProjectFile, tmp)) { lines.push_back(tmp); } oldProjectFile.close(); cmGeneratedFileStream fout(filename.c_str()); if (!fout) { return; } for (std::vector::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, "") != CM_NULLPTR) || (strstr(line, "") != CM_NULLPTR) || (strstr(line, "") != CM_NULLPTR) || (strstr(line, "") != CM_NULLPTR) || (strstr(line, "") != CM_NULLPTR) || (strstr(line, "") != CM_NULLPTR)) { continue; } // output the line from the file if it is not one of the above tags fout << *it << "\n"; // if this is the tag output the stuff that goes in the // general tag if (strstr(line, "")) { fout << " KDevCustomProject\n"; fout << " " << projectDir << "\n"; // this one is important fout << " true\n"; // and this one } // inside kdevcustomproject the must be put if (strstr(line, "")) { fout << " " << outputDir << "\n"; } // buildtool and builddir go inside if (strstr(line, "")) { fout << " make\n"; fout << " " << outputDir << "\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; } cmXMLWriter xml(fout); // 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"; } xml.StartDocument(); xml.StartElement("kdevelop"); xml.StartElement("general"); xml.Element("author", ""); xml.Element("email", ""); xml.Element("version", "$VERSION$"); xml.Element("projectmanagement", "KDevCustomProject"); xml.Element("primarylanguage", primaryLanguage); xml.Element("ignoreparts"); xml.Element("projectdirectory", projectDir); // this one is important xml.Element("absoluteprojectpath", "true"); // and this one // setup additional languages xml.StartElement("secondaryLanguages"); if (enableFortran && enableCxx) { xml.Element("language", "Fortran"); } if (enableCxx) { xml.Element("language", "C"); } xml.EndElement(); if (hasSvn) { xml.Element("versioncontrol", "kdevsubversion"); } else if (hasCvs) { xml.Element("versioncontrol", "kdevcvsservice"); } xml.EndElement(); // general xml.StartElement("kdevcustomproject"); xml.Element("filelistdirectory", outputDir); xml.StartElement("run"); xml.Element("mainprogram", executable); xml.Element("directoryradio", "custom"); xml.Element("customdirectory", outputDir); xml.Element("programargs", ""); xml.Element("terminal", "false"); xml.Element("autocompile", "true"); xml.Element("envvars"); xml.EndElement(); xml.StartElement("build"); xml.Element("buildtool", "make"); // this one is important xml.Element("builddir", outputDir); // and this one xml.EndElement(); xml.StartElement("make"); xml.Element("abortonerror", "false"); xml.Element("numberofjobs", 1); xml.Element("dontact", "false"); xml.Element("makebin", this->GlobalGenerator->GetLocalGenerators()[0] ->GetMakefile() ->GetRequiredDefinition("CMAKE_MAKE_PROGRAM")); xml.Element("selectedenvironment", "default"); xml.StartElement("environments"); xml.StartElement("default"); xml.StartElement("envvar"); xml.Attribute("value", 1); xml.Attribute("name", "VERBOSE"); xml.EndElement(); xml.StartElement("envvar"); xml.Attribute("value", 1); xml.Attribute("name", "CMAKE_NO_VERBOSE"); xml.EndElement(); xml.EndElement(); // default xml.EndElement(); // environments xml.EndElement(); // make xml.StartElement("blacklist"); for (std::vector::const_iterator dirIt = this->Blacklist.begin(); dirIt != this->Blacklist.end(); ++dirIt) { xml.Element("path", *dirIt); } xml.EndElement(); xml.EndElement(); // kdevcustomproject xml.StartElement("kdevfilecreate"); xml.Element("filetypes"); xml.StartElement("useglobaltypes"); xml.StartElement("type"); xml.Attribute("ext", "ui"); xml.EndElement(); xml.StartElement("type"); xml.Attribute("ext", "cpp"); xml.EndElement(); xml.StartElement("type"); xml.Attribute("ext", "h"); xml.EndElement(); xml.EndElement(); // useglobaltypes xml.EndElement(); // kdevfilecreate xml.StartElement("kdevdoctreeview"); xml.StartElement("projectdoc"); xml.Element("userdocDir", "html/"); xml.Element("apidocDir", "html/"); xml.EndElement(); // projectdoc xml.Element("ignoreqt_xml"); xml.Element("ignoredoxygen"); xml.Element("ignorekdocs"); xml.Element("ignoretocs"); xml.Element("ignoredevhelp"); xml.EndElement(); // kdevdoctreeview; if (enableCxx) { xml.StartElement("cppsupportpart"); xml.StartElement("filetemplates"); xml.Element("interfacesuffix", ".h"); xml.Element("implementationsuffix", ".cpp"); xml.EndElement(); // filetemplates xml.EndElement(); // cppsupportpart xml.StartElement("kdevcppsupport"); xml.StartElement("codecompletion"); xml.Element("includeGlobalFunctions", "true"); xml.Element("includeTypes", "true"); xml.Element("includeEnums", "true"); xml.Element("includeTypedefs", "false"); xml.Element("automaticCodeCompletion", "true"); xml.Element("automaticArgumentsHint", "true"); xml.Element("automaticHeaderCompletion", "true"); xml.Element("codeCompletionDelay", 250); xml.Element("argumentsHintDelay", 400); xml.Element("headerCompletionDelay", 250); xml.EndElement(); // codecompletion xml.Element("references"); xml.EndElement(); // kdevcppsupport; } if (enableFortran) { xml.StartElement("kdevfortransupport"); xml.StartElement("ftnchek"); xml.Element("division", "false"); xml.Element("extern", "false"); xml.Element("declare", "false"); xml.Element("pure", "false"); xml.Element("argumentsall", "false"); xml.Element("commonall", "false"); xml.Element("truncationall", "false"); xml.Element("usageall", "false"); xml.Element("f77all", "false"); xml.Element("portabilityall", "false"); xml.Element("argumentsonly"); xml.Element("commononly"); xml.Element("truncationonly"); xml.Element("usageonly"); xml.Element("f77only"); xml.Element("portabilityonly"); xml.EndElement(); // ftnchek xml.EndElement(); // kdevfortransupport; } // set up file groups. maybe this can be used with the CMake SOURCE_GROUP() // command xml.StartElement("kdevfileview"); xml.StartElement("groups"); xml.StartElement("group"); xml.Attribute("pattern", cmakeFilePattern); xml.Attribute("name", "CMake"); xml.EndElement(); if (enableCxx) { xml.StartElement("group"); xml.Attribute("pattern", "*.h;*.hxx;*.hpp"); xml.Attribute("name", "Header"); xml.EndElement(); xml.StartElement("group"); xml.Attribute("pattern", "*.c"); xml.Attribute("name", "C Sources"); xml.EndElement(); xml.StartElement("group"); xml.Attribute("pattern", "*.cpp;*.C;*.cxx;*.cc"); xml.Attribute("name", "C++ Sources"); xml.EndElement(); } if (enableFortran) { xml.StartElement("group"); xml.Attribute("pattern", "*.f;*.F;*.f77;*.F77;*.f90;*.F90;*.for;*.f95;*.F95"); xml.Attribute("name", "Fortran Sources"); xml.EndElement(); } xml.StartElement("group"); xml.Attribute("pattern", "*.ui"); xml.Attribute("name", "Qt Designer files"); xml.EndElement(); xml.Element("hidenonprojectfiles", "true"); xml.EndElement(); // groups xml.StartElement("tree"); xml.Element("hidepatterns", "*.o,*.lo,CVS,*~,cmake*"); xml.Element("hidenonprojectfiles", "true"); xml.EndElement(); // tree xml.EndElement(); // kdevfileview xml.EndElement(); // kdevelop; xml.EndDocument(); 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; } cmXMLWriter sesxml(devses); sesxml.StartDocument("UTF-8"); sesxml.Doctype("KDevPrjSession"); sesxml.StartElement("KDevPrjSession"); sesxml.StartElement("DocsAndViews"); sesxml.Attribute("NumberOfDocuments", 1); sesxml.StartElement("Doc0"); sesxml.Attribute("NumberOfViews", 1); sesxml.Attribute("URL", "file://" + fileToOpen); sesxml.StartElement("View0"); sesxml.Attribute("line", 0); sesxml.Attribute("Type", "Source"); sesxml.EndElement(); // View0 sesxml.EndElement(); // Doc0 sesxml.EndElement(); // DocsAndViews sesxml.EndElement(); // KDevPrjSession; }