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 "cmExtraCodeBlocksGenerator.h"
11 #include <cmext/algorithm>
13 #include "cmAlgorithms.h"
14 #include "cmGeneratedFileStream.h"
15 #include "cmGeneratorTarget.h"
16 #include "cmGlobalGenerator.h"
17 #include "cmLocalGenerator.h"
18 #include "cmMakefile.h"
20 #include "cmSourceFile.h"
21 #include "cmStateTypes.h"
22 #include "cmStringAlgorithms.h"
23 #include "cmSystemTools.h"
25 #include "cmXMLWriter.h"
30 http://www.codeblocks.org
33 http://wiki.codeblocks.org/index.php?title=File_formats_description
34 http://wiki.codeblocks.org/index.php?title=Workspace_file
35 http://wiki.codeblocks.org/index.php?title=Project_file
38 http://forums.codeblocks.org/index.php/topic,6789.0.html
41 cmExtraCodeBlocksGenerator::cmExtraCodeBlocksGenerator() = default;
43 cmExternalMakefileProjectGeneratorFactory*
44 cmExtraCodeBlocksGenerator::GetFactory()
46 static cmExternalMakefileProjectGeneratorSimpleFactory<
47 cmExtraCodeBlocksGenerator>
48 factory("CodeBlocks", "Generates CodeBlocks project files.");
50 if (factory.GetSupportedGlobalGenerators().empty()) {
52 factory.AddSupportedGlobalGenerator("MinGW Makefiles");
53 factory.AddSupportedGlobalGenerator("NMake Makefiles");
54 factory.AddSupportedGlobalGenerator("NMake Makefiles JOM");
55 // disable until somebody actually tests it:
56 // this->AddSupportedGlobalGenerator("MSYS Makefiles");
58 factory.AddSupportedGlobalGenerator("Ninja");
59 factory.AddSupportedGlobalGenerator("Unix Makefiles");
65 void cmExtraCodeBlocksGenerator::Generate()
67 // for each sub project in the project create a codeblocks project
68 for (auto const& it : this->GlobalGenerator->GetProjectMap()) {
69 // create a project file
70 this->CreateProjectFile(it.second);
74 /* create the project file */
75 void cmExtraCodeBlocksGenerator::CreateProjectFile(
76 const std::vector<cmLocalGenerator*>& lgs)
78 std::string outputDir = lgs[0]->GetCurrentBinaryDirectory();
79 std::string projectName = lgs[0]->GetProjectName();
81 std::string filename = cmStrCat(outputDir, '/', projectName, ".cbp");
82 std::string sessionFilename =
83 cmStrCat(outputDir, '/', projectName, ".layout");
85 this->CreateNewProjectFile(lgs, filename);
88 /* Tree is used to create a "Virtual Folder" in CodeBlocks, in which all
89 CMake files this project depends on will be put. This means additionally
90 to the "Sources" and "Headers" virtual folders of CodeBlocks, there will
91 now also be a "CMake Files" virtual folder.
92 Patch by Daniel Teske <daniel.teske AT nokia.com> (which use C::B project
93 files in QtCreator).*/
96 std::string path; // only one component of the path
97 std::vector<Tree> folders;
98 std::set<std::string> files;
99 void InsertPath(const std::vector<std::string>& split,
100 std::vector<std::string>::size_type start,
101 const std::string& fileName);
102 void BuildVirtualFolder(cmXMLWriter& xml) const;
103 void BuildVirtualFolderImpl(std::string& virtualFolders,
104 const std::string& prefix) const;
105 void BuildUnit(cmXMLWriter& xml, const std::string& fsPath) const;
106 void BuildUnitImpl(cmXMLWriter& xml, const std::string& virtualFolderPath,
107 const std::string& fsPath) const;
110 void Tree::InsertPath(const std::vector<std::string>& split,
111 std::vector<std::string>::size_type start,
112 const std::string& fileName)
114 if (start == split.size()) {
115 this->files.insert(fileName);
118 for (Tree& folder : this->folders) {
119 if (folder.path == split[start]) {
120 if (start + 1 < split.size()) {
121 folder.InsertPath(split, start + 1, fileName);
124 // last part of split
125 folder.files.insert(fileName);
129 // Not found in folders, thus insert
131 newFolder.path = split[start];
132 if (start + 1 < split.size()) {
133 newFolder.InsertPath(split, start + 1, fileName);
134 this->folders.push_back(newFolder);
137 // last part of split
138 newFolder.files.insert(fileName);
139 this->folders.push_back(newFolder);
142 void Tree::BuildVirtualFolder(cmXMLWriter& xml) const
144 xml.StartElement("Option");
145 std::string virtualFolders = "CMake Files\\;";
146 for (Tree const& folder : this->folders) {
147 folder.BuildVirtualFolderImpl(virtualFolders, "");
149 xml.Attribute("virtualFolders", virtualFolders);
153 void Tree::BuildVirtualFolderImpl(std::string& virtualFolders,
154 const std::string& prefix) const
156 virtualFolders += "CMake Files\\" + prefix + this->path + "\\;";
157 for (Tree const& folder : this->folders) {
158 folder.BuildVirtualFolderImpl(virtualFolders, prefix + this->path + "\\");
162 void Tree::BuildUnit(cmXMLWriter& xml, const std::string& fsPath) const
164 for (std::string const& f : this->files) {
165 xml.StartElement("Unit");
166 xml.Attribute("filename", fsPath + f);
168 xml.StartElement("Option");
169 xml.Attribute("virtualFolder", "CMake Files\\");
174 for (Tree const& folder : this->folders) {
175 folder.BuildUnitImpl(xml, "", fsPath);
179 void Tree::BuildUnitImpl(cmXMLWriter& xml,
180 const std::string& virtualFolderPath,
181 const std::string& fsPath) const
183 for (std::string const& f : this->files) {
184 xml.StartElement("Unit");
185 xml.Attribute("filename", cmStrCat(fsPath, this->path, "/", f));
187 xml.StartElement("Option");
190 cmStrCat("CMake Files\\", virtualFolderPath, this->path, "\\"));
195 for (Tree const& folder : this->folders) {
196 folder.BuildUnitImpl(xml, cmStrCat(virtualFolderPath, this->path, "\\"),
197 cmStrCat(fsPath, this->path, "/"));
201 void cmExtraCodeBlocksGenerator::CreateNewProjectFile(
202 const std::vector<cmLocalGenerator*>& lgs, const std::string& filename)
204 const cmMakefile* mf = lgs[0]->GetMakefile();
205 cmGeneratedFileStream fout(filename);
212 // build tree of virtual folders
213 for (auto const& it : this->GlobalGenerator->GetProjectMap()) {
215 std::vector<std::string> listFiles;
216 for (cmLocalGenerator* lg : it.second) {
217 cm::append(listFiles, lg->GetMakefile()->GetListFiles());
221 for (std::string const& listFile : listFiles) {
222 // don't put cmake's own files into the project (#12110):
223 if (cmHasPrefix(listFile, cmSystemTools::GetCMakeRoot())) {
227 const std::string& relative = cmSystemTools::RelativePath(
228 it.second[0]->GetSourceDirectory(), listFile);
229 std::vector<std::string> split;
230 cmSystemTools::SplitPath(relative, split, false);
231 // Split filename from path
232 std::string fileName = *(split.end() - 1);
233 split.erase(split.end() - 1, split.end());
235 // We don't want paths with CMakeFiles in them
237 // In speedcrunch those where purely internal
239 // Also we can disable external (outside the project) files by setting ON
240 // CMAKE_CODEBLOCKS_EXCLUDE_EXTERNAL_FILES variable.
241 const bool excludeExternal = it.second[0]->GetMakefile()->IsOn(
242 "CMAKE_CODEBLOCKS_EXCLUDE_EXTERNAL_FILES");
243 if (!split.empty() &&
244 (!excludeExternal || (relative.find("..") == std::string::npos)) &&
245 relative.find("CMakeFiles") == std::string::npos) {
246 tree.InsertPath(split, 1, fileName);
251 // figure out the compiler
252 std::string compiler = this->GetCBCompilerId(mf);
253 const std::string& make = mf->GetRequiredDefinition("CMAKE_MAKE_PROGRAM");
254 const std::string& makeArgs =
255 mf->GetSafeDefinition("CMAKE_CODEBLOCKS_MAKE_ARGUMENTS");
257 cmXMLWriter xml(fout);
259 xml.StartElement("CodeBlocks_project_file");
261 xml.StartElement("FileVersion");
262 xml.Attribute("major", 1);
263 xml.Attribute("minor", 6);
266 xml.StartElement("Project");
268 xml.StartElement("Option");
269 xml.Attribute("title", lgs[0]->GetProjectName());
272 xml.StartElement("Option");
273 xml.Attribute("makefile_is_custom", 1);
276 xml.StartElement("Option");
277 xml.Attribute("compiler", compiler);
280 // Now build a virtual tree
281 tree.BuildVirtualFolder(xml);
283 xml.StartElement("Build");
285 this->AppendTarget(xml, "all", nullptr, make, lgs[0], compiler, makeArgs);
287 // add all executable and library targets and some of the GLOBAL
288 // and UTILITY targets
289 for (cmLocalGenerator* lg : lgs) {
290 const auto& targets = lg->GetGeneratorTargets();
291 for (const auto& target : targets) {
292 std::string targetName = target->GetName();
293 switch (target->GetType()) {
294 case cmStateEnums::GLOBAL_TARGET: {
295 // Only add the global targets from CMAKE_BINARY_DIR,
296 // not from the subdirs
297 if (lg->GetCurrentBinaryDirectory() == lg->GetBinaryDirectory()) {
298 this->AppendTarget(xml, targetName, nullptr, make, lg, compiler,
302 case cmStateEnums::UTILITY:
303 // Add all utility targets, except the Nightly/Continuous/
304 // Experimental-"sub"targets as e.g. NightlyStart
305 if ((cmHasLiteralPrefix(targetName, "Nightly") &&
306 (targetName != "Nightly")) ||
307 (cmHasLiteralPrefix(targetName, "Continuous") &&
308 (targetName != "Continuous")) ||
309 (cmHasLiteralPrefix(targetName, "Experimental") &&
310 (targetName != "Experimental"))) {
314 this->AppendTarget(xml, targetName, nullptr, make, lg, compiler,
317 case cmStateEnums::EXECUTABLE:
318 case cmStateEnums::STATIC_LIBRARY:
319 case cmStateEnums::SHARED_LIBRARY:
320 case cmStateEnums::MODULE_LIBRARY:
321 case cmStateEnums::OBJECT_LIBRARY: {
322 cmGeneratorTarget* gt = target.get();
323 this->AppendTarget(xml, targetName, gt, make, lg, compiler,
325 std::string fastTarget = cmStrCat(targetName, "/fast");
326 this->AppendTarget(xml, fastTarget, gt, make, lg, compiler,
335 xml.EndElement(); // Build
337 // Collect all used source files in the project.
338 // Keep a list of C/C++ source files which might have an accompanying header
339 // that should be looked for.
340 using all_files_map_t = std::map<std::string, CbpUnit>;
341 all_files_map_t allFiles;
342 std::vector<std::string> cFiles;
344 auto* cm = this->GlobalGenerator->GetCMakeInstance();
346 for (cmLocalGenerator* lg : lgs) {
347 cmMakefile* makefile = lg->GetMakefile();
348 const auto& targets = lg->GetGeneratorTargets();
349 for (const auto& target : targets) {
350 switch (target->GetType()) {
351 case cmStateEnums::EXECUTABLE:
352 case cmStateEnums::STATIC_LIBRARY:
353 case cmStateEnums::SHARED_LIBRARY:
354 case cmStateEnums::MODULE_LIBRARY:
355 case cmStateEnums::OBJECT_LIBRARY:
356 case cmStateEnums::UTILITY: // can have sources since 2.6.3
358 std::vector<cmSourceFile*> sources;
359 target->GetSourceFiles(
360 sources, makefile->GetSafeDefinition("CMAKE_BUILD_TYPE"));
361 for (cmSourceFile* s : sources) {
362 // don't add source files from UTILITY target which have the
363 // GENERATED property set:
364 if (target->GetType() == cmStateEnums::UTILITY &&
365 s->GetIsGenerated()) {
369 // check whether it is a C/C++/CUDA/HIP implementation file
370 bool isCFile = false;
371 std::string lang = s->GetOrDetermineLanguage();
372 if (lang == "C" || lang == "CXX" || lang == "CUDA" ||
374 std::string const& srcext = s->GetExtension();
375 isCFile = cm->IsACLikeSourceExtension(srcext);
378 std::string const& fullPath = s->ResolveFullPath();
380 // Check file position relative to project root dir.
381 const std::string relative =
382 cmSystemTools::RelativePath(lg->GetSourceDirectory(), fullPath);
383 // Do not add this file if it has ".." in relative path and
384 // if CMAKE_CODEBLOCKS_EXCLUDE_EXTERNAL_FILES variable is on.
385 const bool excludeExternal = lg->GetMakefile()->IsOn(
386 "CMAKE_CODEBLOCKS_EXCLUDE_EXTERNAL_FILES");
387 if (excludeExternal &&
388 (relative.find("..") != std::string::npos)) {
393 cFiles.push_back(fullPath);
396 CbpUnit& cbpUnit = allFiles[fullPath];
397 cbpUnit.Targets.push_back(target.get());
406 std::vector<std::string> const& headerExts =
407 this->GlobalGenerator->GetCMakeInstance()->GetHeaderExtensions();
409 // The following loop tries to add header files matching to implementation
410 // files to the project. It does that by iterating over all
411 // C/C++ source files,
412 // replacing the file name extension with ".h" and checks whether such a
413 // file exists. If it does, it is inserted into the map of files.
414 // A very similar version of that code exists also in the CodeLite
415 // project generator.
416 for (std::string const& fileName : cFiles) {
417 std::string headerBasename =
418 cmStrCat(cmSystemTools::GetFilenamePath(fileName), '/',
419 cmSystemTools::GetFilenameWithoutExtension(fileName));
421 // check if there's a matching header around
422 for (std::string const& ext : headerExts) {
423 std::string hname = cmStrCat(headerBasename, '.', ext);
424 // if it's already in the set, don't check if it exists on disk
425 if (allFiles.find(hname) != allFiles.end()) {
429 if (cmSystemTools::FileExists(hname)) {
430 allFiles[hname].Targets = allFiles[fileName].Targets;
436 // insert all source files in the CodeBlocks project
437 for (auto const& s : allFiles) {
438 std::string const& unitFilename = s.first;
439 CbpUnit const& unit = s.second;
441 xml.StartElement("Unit");
442 xml.Attribute("filename", unitFilename);
444 for (cmGeneratorTarget const* tgt : unit.Targets) {
445 xml.StartElement("Option");
446 xml.Attribute("target", tgt->GetName());
453 // Add CMakeLists.txt
454 tree.BuildUnit(xml, mf->GetHomeDirectory() + "/");
456 xml.EndElement(); // Project
457 xml.EndElement(); // CodeBlocks_project_file
461 // Write a dummy file for OBJECT libraries, so C::B can reference some file
462 std::string cmExtraCodeBlocksGenerator::CreateDummyTargetFile(
463 cmLocalGenerator* lg, cmGeneratorTarget* target) const
465 // this file doesn't seem to be used by C::B in custom makefile mode,
466 // but we generate a unique file for each OBJECT library so in case
467 // C::B uses it in some way, the targets don't interfere with each other.
468 std::string filename = cmStrCat(lg->GetCurrentBinaryDirectory(), '/',
469 lg->GetTargetDirectory(target), '/',
470 target->GetName(), ".objlib");
471 cmGeneratedFileStream fout(filename);
473 /* clang-format off */
474 fout << "# This is a dummy file for the OBJECT library "
476 << " for the CMake CodeBlocks project generator.\n"
477 << "# Don't edit, this file will be overwritten.\n";
478 /* clang-format on */
483 // Generate the xml code for one target.
484 void cmExtraCodeBlocksGenerator::AppendTarget(
485 cmXMLWriter& xml, const std::string& targetName, cmGeneratorTarget* target,
486 const std::string& make, const cmLocalGenerator* lg,
487 const std::string& compiler, const std::string& makeFlags)
489 cmMakefile const* makefile = lg->GetMakefile();
490 std::string makefileName =
491 cmStrCat(lg->GetCurrentBinaryDirectory(), "/Makefile");
493 xml.StartElement("Target");
494 xml.Attribute("title", targetName);
496 if (target != nullptr) {
497 int cbTargetType = this->GetCBTargetType(target);
498 std::string workingDir = lg->GetCurrentBinaryDirectory();
499 if (target->GetType() == cmStateEnums::EXECUTABLE) {
500 // Determine the directory where the executable target is created, and
501 // set the working directory to this dir.
502 cmValue runtimeOutputDir =
503 makefile->GetDefinition("CMAKE_RUNTIME_OUTPUT_DIRECTORY");
504 if (runtimeOutputDir) {
505 workingDir = *runtimeOutputDir;
507 cmValue executableOutputDir =
508 makefile->GetDefinition("EXECUTABLE_OUTPUT_PATH");
509 if (executableOutputDir) {
510 workingDir = *executableOutputDir;
515 std::string buildType = makefile->GetSafeDefinition("CMAKE_BUILD_TYPE");
516 std::string location;
517 if (target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
519 this->CreateDummyTargetFile(const_cast<cmLocalGenerator*>(lg), target);
521 location = target->GetLocation(buildType);
524 xml.StartElement("Option");
525 xml.Attribute("output", location);
526 xml.Attribute("prefix_auto", 0);
527 xml.Attribute("extension_auto", 0);
530 xml.StartElement("Option");
531 xml.Attribute("working_dir", workingDir);
534 xml.StartElement("Option");
535 xml.Attribute("object_output", "./");
538 xml.StartElement("Option");
539 xml.Attribute("type", cbTargetType);
542 xml.StartElement("Option");
543 xml.Attribute("compiler", compiler);
546 xml.StartElement("Compiler");
548 // the compilerdefines for this target
549 std::vector<std::string> cdefs;
550 target->GetCompileDefinitions(cdefs, buildType, "C");
553 for (std::string const& d : cdefs) {
554 xml.StartElement("Add");
555 xml.Attribute("option", "-D" + d);
559 // the include directories for this target
560 std::vector<std::string> allIncludeDirs;
562 std::vector<std::string> includes;
563 lg->GetIncludeDirectories(includes, target, "C", buildType);
564 cm::append(allIncludeDirs, includes);
567 std::string systemIncludeDirs = makefile->GetSafeDefinition(
568 "CMAKE_EXTRA_GENERATOR_CXX_SYSTEM_INCLUDE_DIRS");
569 if (!systemIncludeDirs.empty()) {
570 cm::append(allIncludeDirs, cmExpandedList(systemIncludeDirs));
573 systemIncludeDirs = makefile->GetSafeDefinition(
574 "CMAKE_EXTRA_GENERATOR_C_SYSTEM_INCLUDE_DIRS");
575 if (!systemIncludeDirs.empty()) {
576 cm::append(allIncludeDirs, cmExpandedList(systemIncludeDirs));
579 auto end = cmRemoveDuplicates(allIncludeDirs);
581 for (std::string const& str : cmMakeRange(allIncludeDirs.cbegin(), end)) {
582 xml.StartElement("Add");
583 xml.Attribute("directory", str);
587 xml.EndElement(); // Compiler
588 } else // e.g. all and the GLOBAL and UTILITY targets
590 xml.StartElement("Option");
591 xml.Attribute("working_dir", lg->GetCurrentBinaryDirectory());
594 xml.StartElement("Option");
595 xml.Attribute("type", 4);
599 xml.StartElement("MakeCommands");
601 xml.StartElement("Build");
604 this->BuildMakeCommand(make, makefileName, targetName, makeFlags));
607 xml.StartElement("CompileFile");
610 this->BuildMakeCommand(make, makefileName, "\"$file\"", makeFlags));
613 xml.StartElement("Clean");
615 "command", this->BuildMakeCommand(make, makefileName, "clean", makeFlags));
618 xml.StartElement("DistClean");
620 "command", this->BuildMakeCommand(make, makefileName, "clean", makeFlags));
623 xml.EndElement(); // MakeCommands
624 xml.EndElement(); // Target
627 // Translate the cmake compiler id into the CodeBlocks compiler id
628 std::string cmExtraCodeBlocksGenerator::GetCBCompilerId(const cmMakefile* mf)
630 // allow the user to overwrite the detected compiler
631 std::string userCompiler =
632 mf->GetSafeDefinition("CMAKE_CODEBLOCKS_COMPILER_ID");
633 if (!userCompiler.empty()) {
637 // figure out which language to use
638 // for now care only for C, C++, and Fortran
640 // projects with C/C++ and Fortran are handled as C/C++ projects
641 bool pureFortran = false;
642 std::string compilerIdVar;
643 if (this->GlobalGenerator->GetLanguageEnabled("CXX")) {
644 compilerIdVar = "CMAKE_CXX_COMPILER_ID";
645 } else if (this->GlobalGenerator->GetLanguageEnabled("C")) {
646 compilerIdVar = "CMAKE_C_COMPILER_ID";
647 } else if (this->GlobalGenerator->GetLanguageEnabled("Fortran")) {
648 compilerIdVar = "CMAKE_Fortran_COMPILER_ID";
652 std::string const& compilerId = mf->GetSafeDefinition(compilerIdVar);
653 std::string compiler = "gcc"; // default to gcc
654 if (compilerId == "MSVC") {
655 if (mf->IsDefinitionSet("MSVC10")) {
660 } else if (compilerId == "Borland") {
662 } else if (compilerId == "SDCC") {
664 } else if (compilerId == "Intel") {
665 if (pureFortran && mf->IsDefinitionSet("WIN32")) {
666 compiler = "ifcwin"; // Intel Fortran for Windows (known by cbFortran)
670 } else if (compilerId == "Watcom" || compilerId == "OpenWatcom") {
672 } else if (compilerId == "Clang") {
674 } else if (compilerId == "PGI") {
676 compiler = "pgifortran";
678 compiler = "pgi"; // does not exist as default in CodeBlocks 16.01
680 } else if (compilerId == "LCC") {
682 compiler = "lfortran";
686 } else if (compilerId == "GNU") {
688 compiler = "gfortran";
696 // Translate the cmake target type into the CodeBlocks target type id
697 int cmExtraCodeBlocksGenerator::GetCBTargetType(cmGeneratorTarget* target)
699 switch (target->GetType()) {
700 case cmStateEnums::EXECUTABLE:
701 if ((target->IsWin32Executable(
702 target->Makefile->GetSafeDefinition("CMAKE_BUILD_TYPE"))) ||
703 (target->GetPropertyAsBool("MACOSX_BUNDLE"))) {
707 case cmStateEnums::STATIC_LIBRARY:
708 case cmStateEnums::OBJECT_LIBRARY:
710 case cmStateEnums::SHARED_LIBRARY:
711 case cmStateEnums::MODULE_LIBRARY:
718 // Create the command line for building the given target using the selected
720 std::string cmExtraCodeBlocksGenerator::BuildMakeCommand(
721 const std::string& make, const std::string& makefile,
722 const std::string& target, const std::string& makeFlags)
724 std::string command = make;
725 if (!makeFlags.empty()) {
727 command += makeFlags;
730 std::string generator = this->GlobalGenerator->GetName();
731 if (generator == "NMake Makefiles" || generator == "NMake Makefiles JOM") {
732 // For Windows ConvertToOutputPath already adds quotes when required.
733 // These need to be escaped, see
734 // https://gitlab.kitware.com/cmake/cmake/-/issues/13952
735 std::string makefileName = cmSystemTools::ConvertToOutputPath(makefile);
736 command += " /NOLOGO /f ";
737 command += makefileName;
738 command += " VERBOSE=1 ";
740 } else if (generator == "MinGW Makefiles") {
741 // no escaping of spaces in this case, see
742 // https://gitlab.kitware.com/cmake/cmake/-/issues/10014
743 std::string const& makefileName = makefile;
745 command += makefileName;
747 command += " VERBOSE=1 ";
749 } else if (generator == "Ninja") {
753 std::string makefileName = cmSystemTools::ConvertToOutputPath(makefile);
755 command += makefileName;
757 command += " VERBOSE=1 ";