cmGlobalKdevelopGenerator.cxx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmGlobalKdevelopGenerator.h"
  4. #include "cmGeneratedFileStream.h"
  5. #include "cmGeneratorTarget.h"
  6. #include "cmGlobalGenerator.h"
  7. #include "cmLocalGenerator.h"
  8. #include "cmMakefile.h"
  9. #include "cmSourceFile.h"
  10. #include "cmStateTypes.h"
  11. #include "cmSystemTools.h"
  12. #include "cmTarget.h"
  13. #include "cmXMLWriter.h"
  14. #include "cmake.h"
  15. #include "cmsys/Directory.hxx"
  16. #include "cmsys/FStream.hxx"
  17. #include <set>
  18. #include <string.h>
  19. #include <utility>
  20. cmGlobalKdevelopGenerator::cmGlobalKdevelopGenerator()
  21. : cmExternalMakefileProjectGenerator()
  22. {
  23. }
  24. cmExternalMakefileProjectGeneratorFactory*
  25. cmGlobalKdevelopGenerator::GetFactory()
  26. {
  27. static cmExternalMakefileProjectGeneratorSimpleFactory<
  28. cmGlobalKdevelopGenerator>
  29. factory("KDevelop3", "Generates KDevelop 3 project files.");
  30. if (factory.GetSupportedGlobalGenerators().empty()) {
  31. factory.AddSupportedGlobalGenerator("Unix Makefiles");
  32. #ifdef CMAKE_USE_NINJA
  33. factory.AddSupportedGlobalGenerator("Ninja");
  34. #endif
  35. factory.Aliases.push_back("KDevelop3");
  36. }
  37. return &factory;
  38. }
  39. void cmGlobalKdevelopGenerator::Generate()
  40. {
  41. // for each sub project in the project create
  42. // a kdevelop project
  43. for (auto const& it : this->GlobalGenerator->GetProjectMap()) {
  44. std::string outputDir = it.second[0]->GetCurrentBinaryDirectory();
  45. std::string projectDir = it.second[0]->GetSourceDirectory();
  46. std::string projectName = it.second[0]->GetProjectName();
  47. std::string cmakeFilePattern("CMakeLists.txt;*.cmake;");
  48. std::string fileToOpen;
  49. const std::vector<cmLocalGenerator*>& lgs = it.second;
  50. // create the project.kdevelop.filelist file
  51. if (!this->CreateFilelistFile(lgs, outputDir, projectDir, projectName,
  52. cmakeFilePattern, fileToOpen)) {
  53. cmSystemTools::Error("Can not create filelist file");
  54. return;
  55. }
  56. // try to find the name of an executable so we have something to
  57. // run from kdevelop for now just pick the first executable found
  58. std::string executable;
  59. for (cmLocalGenerator* lg : lgs) {
  60. std::vector<cmGeneratorTarget*> const& targets =
  61. lg->GetGeneratorTargets();
  62. for (cmGeneratorTarget* target : targets) {
  63. if (target->GetType() == cmStateEnums::EXECUTABLE) {
  64. executable = target->GetLocation("");
  65. break;
  66. }
  67. }
  68. if (!executable.empty()) {
  69. break;
  70. }
  71. }
  72. // now create a project file
  73. this->CreateProjectFile(outputDir, projectDir, projectName, executable,
  74. cmakeFilePattern, fileToOpen);
  75. }
  76. }
  77. bool cmGlobalKdevelopGenerator::CreateFilelistFile(
  78. const std::vector<cmLocalGenerator*>& lgs, const std::string& outputDir,
  79. const std::string& projectDirIn, const std::string& projectname,
  80. std::string& cmakeFilePattern, std::string& fileToOpen)
  81. {
  82. std::string projectDir = projectDirIn + "/";
  83. std::string filename = outputDir + "/" + projectname + ".kdevelop.filelist";
  84. std::set<std::string> files;
  85. std::string tmp;
  86. std::vector<std::string> const& hdrExts =
  87. this->GlobalGenerator->GetCMakeInstance()->GetHeaderExtensions();
  88. for (cmLocalGenerator* lg : lgs) {
  89. cmMakefile* makefile = lg->GetMakefile();
  90. const std::vector<std::string>& listFiles = makefile->GetListFiles();
  91. for (std::string const& listFile : listFiles) {
  92. tmp = listFile;
  93. cmSystemTools::ReplaceString(tmp, projectDir.c_str(), "");
  94. // make sure the file is part of this source tree
  95. if ((tmp[0] != '/') &&
  96. (strstr(tmp.c_str(), cmake::GetCMakeFilesDirectoryPostSlash()) ==
  97. nullptr)) {
  98. files.insert(tmp);
  99. tmp = cmSystemTools::GetFilenameName(tmp);
  100. // add all files which dont match the default
  101. // */CMakeLists.txt;*cmake; to the file pattern
  102. if ((tmp != "CMakeLists.txt") &&
  103. (strstr(tmp.c_str(), ".cmake") == nullptr)) {
  104. cmakeFilePattern += tmp + ";";
  105. }
  106. }
  107. }
  108. // get all sources
  109. const std::vector<cmGeneratorTarget*>& targets = lg->GetGeneratorTargets();
  110. for (cmGeneratorTarget* gt : targets) {
  111. std::vector<cmSourceFile*> sources;
  112. gt->GetSourceFiles(sources, gt->Target->GetMakefile()->GetSafeDefinition(
  113. "CMAKE_BUILD_TYPE"));
  114. for (cmSourceFile* sf : sources) {
  115. tmp = sf->GetFullPath();
  116. std::string headerBasename = cmSystemTools::GetFilenamePath(tmp);
  117. headerBasename += "/";
  118. headerBasename += cmSystemTools::GetFilenameWithoutExtension(tmp);
  119. cmSystemTools::ReplaceString(tmp, projectDir.c_str(), "");
  120. if ((tmp[0] != '/') &&
  121. (strstr(tmp.c_str(), cmake::GetCMakeFilesDirectoryPostSlash()) ==
  122. nullptr) &&
  123. (cmSystemTools::GetFilenameExtension(tmp) != ".moc")) {
  124. files.insert(tmp);
  125. // check if there's a matching header around
  126. for (std::string const& hdrExt : hdrExts) {
  127. std::string hname = headerBasename;
  128. hname += ".";
  129. hname += hdrExt;
  130. if (cmSystemTools::FileExists(hname.c_str())) {
  131. cmSystemTools::ReplaceString(hname, projectDir.c_str(), "");
  132. files.insert(hname);
  133. break;
  134. }
  135. }
  136. }
  137. }
  138. for (std::string const& listFile : listFiles) {
  139. tmp = listFile;
  140. cmSystemTools::ReplaceString(tmp, projectDir.c_str(), "");
  141. if ((tmp[0] != '/') &&
  142. (strstr(tmp.c_str(), cmake::GetCMakeFilesDirectoryPostSlash()) ==
  143. nullptr)) {
  144. files.insert(tmp);
  145. }
  146. }
  147. }
  148. }
  149. // check if the output file already exists and read it
  150. // insert all files which exist into the set of files
  151. cmsys::ifstream oldFilelist(filename.c_str());
  152. if (oldFilelist) {
  153. while (cmSystemTools::GetLineFromStream(oldFilelist, tmp)) {
  154. if (tmp[0] == '/') {
  155. continue;
  156. }
  157. std::string completePath = projectDir + tmp;
  158. if (cmSystemTools::FileExists(completePath.c_str())) {
  159. files.insert(tmp);
  160. }
  161. }
  162. oldFilelist.close();
  163. }
  164. // now write the new filename
  165. cmGeneratedFileStream fout(filename.c_str());
  166. if (!fout) {
  167. return false;
  168. }
  169. fileToOpen = "";
  170. for (std::string const& file : files) {
  171. // get the full path to the file
  172. tmp = cmSystemTools::CollapseFullPath(file, projectDir.c_str());
  173. // just select the first source file
  174. if (fileToOpen.empty()) {
  175. std::string ext = cmSystemTools::GetFilenameExtension(tmp);
  176. if ((ext == ".c") || (ext == ".cc") || (ext == ".cpp") ||
  177. (ext == ".cxx") || (ext == ".C") || (ext == ".h") ||
  178. (ext == ".hpp")) {
  179. fileToOpen = tmp;
  180. }
  181. }
  182. // make it relative to the project dir
  183. cmSystemTools::ReplaceString(tmp, projectDir.c_str(), "");
  184. // only put relative paths
  185. if (!tmp.empty() && tmp[0] != '/') {
  186. fout << tmp << "\n";
  187. }
  188. }
  189. return true;
  190. }
  191. /* create the project file, if it already exists, merge it with the
  192. existing one, otherwise create a new one */
  193. void cmGlobalKdevelopGenerator::CreateProjectFile(
  194. const std::string& outputDir, const std::string& projectDir,
  195. const std::string& projectname, const std::string& executable,
  196. const std::string& cmakeFilePattern, const std::string& fileToOpen)
  197. {
  198. this->Blacklist.clear();
  199. std::string filename = outputDir + "/";
  200. filename += projectname + ".kdevelop";
  201. std::string sessionFilename = outputDir + "/";
  202. sessionFilename += projectname + ".kdevses";
  203. if (cmSystemTools::FileExists(filename.c_str())) {
  204. this->MergeProjectFiles(outputDir, projectDir, filename, executable,
  205. cmakeFilePattern, fileToOpen, sessionFilename);
  206. } else {
  207. // add all subdirectories which are cmake build directories to the
  208. // kdevelop blacklist so they are not monitored for added or removed files
  209. // since this is handled by adding files to the cmake files
  210. cmsys::Directory d;
  211. if (d.Load(projectDir)) {
  212. size_t numf = d.GetNumberOfFiles();
  213. for (unsigned int i = 0; i < numf; i++) {
  214. std::string nextFile = d.GetFile(i);
  215. if ((nextFile != ".") && (nextFile != "..")) {
  216. std::string tmp = projectDir;
  217. tmp += "/";
  218. tmp += nextFile;
  219. if (cmSystemTools::FileIsDirectory(tmp)) {
  220. tmp += "/CMakeCache.txt";
  221. if ((nextFile == "CMakeFiles") ||
  222. (cmSystemTools::FileExists(tmp.c_str()))) {
  223. this->Blacklist.push_back(nextFile);
  224. }
  225. }
  226. }
  227. }
  228. }
  229. this->CreateNewProjectFile(outputDir, projectDir, filename, executable,
  230. cmakeFilePattern, fileToOpen, sessionFilename);
  231. }
  232. }
  233. void cmGlobalKdevelopGenerator::MergeProjectFiles(
  234. const std::string& outputDir, const std::string& projectDir,
  235. const std::string& filename, const std::string& executable,
  236. const std::string& cmakeFilePattern, const std::string& fileToOpen,
  237. const std::string& sessionFilename)
  238. {
  239. cmsys::ifstream oldProjectFile(filename.c_str());
  240. if (!oldProjectFile) {
  241. this->CreateNewProjectFile(outputDir, projectDir, filename, executable,
  242. cmakeFilePattern, fileToOpen, sessionFilename);
  243. return;
  244. }
  245. /* Read the existing project file (line by line), copy all lines
  246. into the new project file, except the ones which can be reliably
  247. set from contents of the CMakeLists.txt */
  248. std::string tmp;
  249. std::vector<std::string> lines;
  250. while (cmSystemTools::GetLineFromStream(oldProjectFile, tmp)) {
  251. lines.push_back(tmp);
  252. }
  253. oldProjectFile.close();
  254. cmGeneratedFileStream fout(filename.c_str());
  255. if (!fout) {
  256. return;
  257. }
  258. for (std::string const& l : lines) {
  259. const char* line = l.c_str();
  260. // skip these tags as they are always replaced
  261. if ((strstr(line, "<projectdirectory>") != nullptr) ||
  262. (strstr(line, "<projectmanagement>") != nullptr) ||
  263. (strstr(line, "<absoluteprojectpath>") != nullptr) ||
  264. (strstr(line, "<filelistdirectory>") != nullptr) ||
  265. (strstr(line, "<buildtool>") != nullptr) ||
  266. (strstr(line, "<builddir>") != nullptr)) {
  267. continue;
  268. }
  269. // output the line from the file if it is not one of the above tags
  270. fout << l << "\n";
  271. // if this is the <general> tag output the stuff that goes in the
  272. // general tag
  273. if (strstr(line, "<general>")) {
  274. fout << " <projectmanagement>KDevCustomProject</projectmanagement>\n";
  275. fout << " <projectdirectory>" << projectDir
  276. << "</projectdirectory>\n"; // this one is important
  277. fout << " <absoluteprojectpath>true</absoluteprojectpath>\n";
  278. // and this one
  279. }
  280. // inside kdevcustomproject the <filelistdirectory> must be put
  281. if (strstr(line, "<kdevcustomproject>")) {
  282. fout << " <filelistdirectory>" << outputDir
  283. << "</filelistdirectory>\n";
  284. }
  285. // buildtool and builddir go inside <build>
  286. if (strstr(line, "<build>")) {
  287. fout << " <buildtool>make</buildtool>\n";
  288. fout << " <builddir>" << outputDir << "</builddir>\n";
  289. }
  290. }
  291. }
  292. void cmGlobalKdevelopGenerator::CreateNewProjectFile(
  293. const std::string& outputDir, const std::string& projectDir,
  294. const std::string& filename, const std::string& executable,
  295. const std::string& cmakeFilePattern, const std::string& fileToOpen,
  296. const std::string& sessionFilename)
  297. {
  298. cmGeneratedFileStream fout(filename.c_str());
  299. if (!fout) {
  300. return;
  301. }
  302. cmXMLWriter xml(fout);
  303. // check for a version control system
  304. bool hasSvn = cmSystemTools::FileExists((projectDir + "/.svn").c_str());
  305. bool hasCvs = cmSystemTools::FileExists((projectDir + "/CVS").c_str());
  306. bool enableCxx = (this->GlobalGenerator->GetLanguageEnabled("C") ||
  307. this->GlobalGenerator->GetLanguageEnabled("CXX"));
  308. bool enableFortran = this->GlobalGenerator->GetLanguageEnabled("Fortran");
  309. std::string primaryLanguage = "C++";
  310. if (enableFortran && !enableCxx) {
  311. primaryLanguage = "Fortran77";
  312. }
  313. xml.StartDocument();
  314. xml.StartElement("kdevelop");
  315. xml.StartElement("general");
  316. xml.Element("author", "");
  317. xml.Element("email", "");
  318. xml.Element("version", "$VERSION$");
  319. xml.Element("projectmanagement", "KDevCustomProject");
  320. xml.Element("primarylanguage", primaryLanguage);
  321. xml.Element("ignoreparts");
  322. xml.Element("projectdirectory", projectDir); // this one is important
  323. xml.Element("absoluteprojectpath", "true"); // and this one
  324. // setup additional languages
  325. xml.StartElement("secondaryLanguages");
  326. if (enableFortran && enableCxx) {
  327. xml.Element("language", "Fortran");
  328. }
  329. if (enableCxx) {
  330. xml.Element("language", "C");
  331. }
  332. xml.EndElement();
  333. if (hasSvn) {
  334. xml.Element("versioncontrol", "kdevsubversion");
  335. } else if (hasCvs) {
  336. xml.Element("versioncontrol", "kdevcvsservice");
  337. }
  338. xml.EndElement(); // general
  339. xml.StartElement("kdevcustomproject");
  340. xml.Element("filelistdirectory", outputDir);
  341. xml.StartElement("run");
  342. xml.Element("mainprogram", executable);
  343. xml.Element("directoryradio", "custom");
  344. xml.Element("customdirectory", outputDir);
  345. xml.Element("programargs", "");
  346. xml.Element("terminal", "false");
  347. xml.Element("autocompile", "true");
  348. xml.Element("envvars");
  349. xml.EndElement();
  350. xml.StartElement("build");
  351. xml.Element("buildtool", "make"); // this one is important
  352. xml.Element("builddir", outputDir); // and this one
  353. xml.EndElement();
  354. xml.StartElement("make");
  355. xml.Element("abortonerror", "false");
  356. xml.Element("numberofjobs", 1);
  357. xml.Element("dontact", "false");
  358. xml.Element("makebin", this->GlobalGenerator->GetLocalGenerators()[0]
  359. ->GetMakefile()
  360. ->GetRequiredDefinition("CMAKE_MAKE_PROGRAM"));
  361. xml.Element("selectedenvironment", "default");
  362. xml.StartElement("environments");
  363. xml.StartElement("default");
  364. xml.StartElement("envvar");
  365. xml.Attribute("value", 1);
  366. xml.Attribute("name", "VERBOSE");
  367. xml.EndElement();
  368. xml.StartElement("envvar");
  369. xml.Attribute("value", 1);
  370. xml.Attribute("name", "CMAKE_NO_VERBOSE");
  371. xml.EndElement();
  372. xml.EndElement(); // default
  373. xml.EndElement(); // environments
  374. xml.EndElement(); // make
  375. xml.StartElement("blacklist");
  376. for (std::string const& dir : this->Blacklist) {
  377. xml.Element("path", dir);
  378. }
  379. xml.EndElement();
  380. xml.EndElement(); // kdevcustomproject
  381. xml.StartElement("kdevfilecreate");
  382. xml.Element("filetypes");
  383. xml.StartElement("useglobaltypes");
  384. xml.StartElement("type");
  385. xml.Attribute("ext", "ui");
  386. xml.EndElement();
  387. xml.StartElement("type");
  388. xml.Attribute("ext", "cpp");
  389. xml.EndElement();
  390. xml.StartElement("type");
  391. xml.Attribute("ext", "h");
  392. xml.EndElement();
  393. xml.EndElement(); // useglobaltypes
  394. xml.EndElement(); // kdevfilecreate
  395. xml.StartElement("kdevdoctreeview");
  396. xml.StartElement("projectdoc");
  397. xml.Element("userdocDir", "html/");
  398. xml.Element("apidocDir", "html/");
  399. xml.EndElement(); // projectdoc
  400. xml.Element("ignoreqt_xml");
  401. xml.Element("ignoredoxygen");
  402. xml.Element("ignorekdocs");
  403. xml.Element("ignoretocs");
  404. xml.Element("ignoredevhelp");
  405. xml.EndElement(); // kdevdoctreeview;
  406. if (enableCxx) {
  407. xml.StartElement("cppsupportpart");
  408. xml.StartElement("filetemplates");
  409. xml.Element("interfacesuffix", ".h");
  410. xml.Element("implementationsuffix", ".cpp");
  411. xml.EndElement(); // filetemplates
  412. xml.EndElement(); // cppsupportpart
  413. xml.StartElement("kdevcppsupport");
  414. xml.StartElement("codecompletion");
  415. xml.Element("includeGlobalFunctions", "true");
  416. xml.Element("includeTypes", "true");
  417. xml.Element("includeEnums", "true");
  418. xml.Element("includeTypedefs", "false");
  419. xml.Element("automaticCodeCompletion", "true");
  420. xml.Element("automaticArgumentsHint", "true");
  421. xml.Element("automaticHeaderCompletion", "true");
  422. xml.Element("codeCompletionDelay", 250);
  423. xml.Element("argumentsHintDelay", 400);
  424. xml.Element("headerCompletionDelay", 250);
  425. xml.EndElement(); // codecompletion
  426. xml.Element("references");
  427. xml.EndElement(); // kdevcppsupport;
  428. }
  429. if (enableFortran) {
  430. xml.StartElement("kdevfortransupport");
  431. xml.StartElement("ftnchek");
  432. xml.Element("division", "false");
  433. xml.Element("extern", "false");
  434. xml.Element("declare", "false");
  435. xml.Element("pure", "false");
  436. xml.Element("argumentsall", "false");
  437. xml.Element("commonall", "false");
  438. xml.Element("truncationall", "false");
  439. xml.Element("usageall", "false");
  440. xml.Element("f77all", "false");
  441. xml.Element("portabilityall", "false");
  442. xml.Element("argumentsonly");
  443. xml.Element("commononly");
  444. xml.Element("truncationonly");
  445. xml.Element("usageonly");
  446. xml.Element("f77only");
  447. xml.Element("portabilityonly");
  448. xml.EndElement(); // ftnchek
  449. xml.EndElement(); // kdevfortransupport;
  450. }
  451. // set up file groups. maybe this can be used with the CMake SOURCE_GROUP()
  452. // command
  453. xml.StartElement("kdevfileview");
  454. xml.StartElement("groups");
  455. xml.StartElement("group");
  456. xml.Attribute("pattern", cmakeFilePattern);
  457. xml.Attribute("name", "CMake");
  458. xml.EndElement();
  459. if (enableCxx) {
  460. xml.StartElement("group");
  461. xml.Attribute("pattern", "*.h;*.hxx;*.hpp");
  462. xml.Attribute("name", "Header");
  463. xml.EndElement();
  464. xml.StartElement("group");
  465. xml.Attribute("pattern", "*.c");
  466. xml.Attribute("name", "C Sources");
  467. xml.EndElement();
  468. xml.StartElement("group");
  469. xml.Attribute("pattern", "*.cpp;*.C;*.cxx;*.cc");
  470. xml.Attribute("name", "C++ Sources");
  471. xml.EndElement();
  472. }
  473. if (enableFortran) {
  474. xml.StartElement("group");
  475. xml.Attribute("pattern",
  476. "*.f;*.F;*.f77;*.F77;*.f90;*.F90;*.for;*.f95;*.F95");
  477. xml.Attribute("name", "Fortran Sources");
  478. xml.EndElement();
  479. }
  480. xml.StartElement("group");
  481. xml.Attribute("pattern", "*.ui");
  482. xml.Attribute("name", "Qt Designer files");
  483. xml.EndElement();
  484. xml.Element("hidenonprojectfiles", "true");
  485. xml.EndElement(); // groups
  486. xml.StartElement("tree");
  487. xml.Element("hidepatterns", "*.o,*.lo,CVS,*~,cmake*");
  488. xml.Element("hidenonprojectfiles", "true");
  489. xml.EndElement(); // tree
  490. xml.EndElement(); // kdevfileview
  491. xml.EndElement(); // kdevelop;
  492. xml.EndDocument();
  493. if (sessionFilename.empty()) {
  494. return;
  495. }
  496. // and a session file, so that kdevelop opens a file if it opens the
  497. // project the first time
  498. cmGeneratedFileStream devses(sessionFilename.c_str());
  499. if (!devses) {
  500. return;
  501. }
  502. cmXMLWriter sesxml(devses);
  503. sesxml.StartDocument("UTF-8");
  504. sesxml.Doctype("KDevPrjSession");
  505. sesxml.StartElement("KDevPrjSession");
  506. sesxml.StartElement("DocsAndViews");
  507. sesxml.Attribute("NumberOfDocuments", 1);
  508. sesxml.StartElement("Doc0");
  509. sesxml.Attribute("NumberOfViews", 1);
  510. sesxml.Attribute("URL", "file://" + fileToOpen);
  511. sesxml.StartElement("View0");
  512. sesxml.Attribute("line", 0);
  513. sesxml.Attribute("Type", "Source");
  514. sesxml.EndElement(); // View0
  515. sesxml.EndElement(); // Doc0
  516. sesxml.EndElement(); // DocsAndViews
  517. sesxml.EndElement(); // KDevPrjSession;
  518. }