/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmExtraCodeLiteGenerator.h" #include "cmGeneratedFileStream.h" #include "cmGeneratorTarget.h" #include "cmGlobalGenerator.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" #include "cmSourceFile.h" #include "cmStateTypes.h" #include "cmSystemTools.h" #include "cmXMLWriter.h" #include "cmake.h" #include "cmsys/SystemInformation.hxx" #include #include #include #include #include cmExtraCodeLiteGenerator::cmExtraCodeLiteGenerator() : cmExternalMakefileProjectGenerator() , ConfigName("NoConfig") , CpuCount(2) { } cmExternalMakefileProjectGeneratorFactory* cmExtraCodeLiteGenerator::GetFactory() { static cmExternalMakefileProjectGeneratorSimpleFactory< cmExtraCodeLiteGenerator> factory("CodeLite", "Generates CodeLite project files."); if (factory.GetSupportedGlobalGenerators().empty()) { #if defined(_WIN32) factory.AddSupportedGlobalGenerator("MinGW Makefiles"); factory.AddSupportedGlobalGenerator("NMake Makefiles"); #endif factory.AddSupportedGlobalGenerator("Ninja"); factory.AddSupportedGlobalGenerator("Unix Makefiles"); } return &factory; } void cmExtraCodeLiteGenerator::Generate() { // Hold root tree information for creating the workspace std::string workspaceProjectName; std::string workspaceOutputDir; std::string workspaceFileName; std::string workspaceSourcePath; const std::map>& projectMap = this->GlobalGenerator->GetProjectMap(); // loop projects and locate the root project. // and extract the information for creating the worspace // root makefile for (auto const& it : projectMap) { const cmMakefile* mf = it.second[0]->GetMakefile(); this->ConfigName = GetConfigurationName(mf); if (strcmp(it.second[0]->GetCurrentBinaryDirectory(), it.second[0]->GetBinaryDirectory()) == 0) { workspaceOutputDir = it.second[0]->GetCurrentBinaryDirectory(); workspaceProjectName = it.second[0]->GetProjectName(); workspaceSourcePath = it.second[0]->GetSourceDirectory(); workspaceFileName = workspaceOutputDir + "/"; workspaceFileName += workspaceProjectName + ".workspace"; this->WorkspacePath = it.second[0]->GetCurrentBinaryDirectory(); ; break; } } cmGeneratedFileStream fout(workspaceFileName.c_str()); cmXMLWriter xml(fout); xml.StartDocument("utf-8"); xml.StartElement("CodeLite_Workspace"); xml.Attribute("Name", workspaceProjectName); bool const targetsAreProjects = this->GlobalGenerator->GlobalSettingIsOn("CMAKE_CODELITE_USE_TARGETS"); std::vector ProjectNames; if (targetsAreProjects) { ProjectNames = CreateProjectsByTarget(&xml); } else { ProjectNames = CreateProjectsByProjectMaps(&xml); } xml.StartElement("BuildMatrix"); xml.StartElement("WorkspaceConfiguration"); xml.Attribute("Name", this->ConfigName); xml.Attribute("Selected", "yes"); for (std::string const& it : ProjectNames) { xml.StartElement("Project"); xml.Attribute("Name", it); xml.Attribute("ConfigName", this->ConfigName); xml.EndElement(); } xml.EndElement(); // WorkspaceConfiguration xml.EndElement(); // BuildMatrix xml.EndElement(); // CodeLite_Workspace } // Create projects where targets are the projects std::vector cmExtraCodeLiteGenerator::CreateProjectsByTarget( cmXMLWriter* xml) { std::vector retval; // for each target in the workspace create a codelite project const std::vector& lgs = this->GlobalGenerator->GetLocalGenerators(); for (cmLocalGenerator* lg : lgs) { for (cmGeneratorTarget* lt : lg->GetGeneratorTargets()) { cmStateEnums::TargetType type = lt->GetType(); std::string outputDir = lg->GetCurrentBinaryDirectory(); std::string targetName = lt->GetName(); std::string filename = outputDir + "/" + targetName + ".project"; retval.push_back(targetName); // Make the project file relative to the workspace std::string relafilename = cmSystemTools::RelativePath( this->WorkspacePath.c_str(), filename.c_str()); std::string visualname = targetName; switch (type) { case cmStateEnums::SHARED_LIBRARY: case cmStateEnums::STATIC_LIBRARY: case cmStateEnums::MODULE_LIBRARY: visualname = "lib" + visualname; CM_FALLTHROUGH; case cmStateEnums::EXECUTABLE: xml->StartElement("Project"); xml->Attribute("Name", visualname); xml->Attribute("Path", relafilename); xml->Attribute("Active", "No"); xml->EndElement(); CreateNewProjectFile(lt, filename); break; default: break; } } } return retval; } // The "older way of doing it. std::vector cmExtraCodeLiteGenerator::CreateProjectsByProjectMaps( cmXMLWriter* xml) { std::vector retval; // for each sub project in the workspace create a codelite project for (auto const& it : this->GlobalGenerator->GetProjectMap()) { std::string outputDir = it.second[0]->GetCurrentBinaryDirectory(); std::string projectName = it.second[0]->GetProjectName(); retval.push_back(projectName); std::string filename = outputDir + "/" + projectName + ".project"; // Make the project file relative to the workspace filename = cmSystemTools::RelativePath(this->WorkspacePath.c_str(), filename.c_str()); // create a project file this->CreateProjectFile(it.second); xml->StartElement("Project"); xml->Attribute("Name", projectName); xml->Attribute("Path", filename); xml->Attribute("Active", "No"); xml->EndElement(); } return retval; } /* create the project file */ void cmExtraCodeLiteGenerator::CreateProjectFile( const std::vector& lgs) { std::string outputDir = lgs[0]->GetCurrentBinaryDirectory(); std::string projectName = lgs[0]->GetProjectName(); std::string filename = outputDir + "/"; filename += projectName + ".project"; this->CreateNewProjectFile(lgs, filename); } std::string cmExtraCodeLiteGenerator::CollectSourceFiles( const cmMakefile* makefile, const cmGeneratorTarget* gt, std::map& cFiles, std::set& otherFiles) { auto cm = this->GlobalGenerator->GetCMakeInstance(); std::string projectType; switch (gt->GetType()) { case cmStateEnums::EXECUTABLE: { projectType = "Executable"; } break; case cmStateEnums::STATIC_LIBRARY: { projectType = "Static Library"; } break; case cmStateEnums::SHARED_LIBRARY: { projectType = "Dynamic Library"; } break; case cmStateEnums::MODULE_LIBRARY: { projectType = "Dynamic Library"; } break; default: // intended fallthrough break; } switch (gt->GetType()) { case cmStateEnums::EXECUTABLE: case cmStateEnums::STATIC_LIBRARY: case cmStateEnums::SHARED_LIBRARY: case cmStateEnums::MODULE_LIBRARY: { std::vector sources; gt->GetSourceFiles(sources, makefile->GetSafeDefinition("CMAKE_BUILD_TYPE")); for (cmSourceFile* s : sources) { // check whether it is a C/C++ implementation file bool isCFile = false; std::string lang = s->GetLanguage(); if (lang == "C" || lang == "CXX") { std::string const& srcext = s->GetExtension(); isCFile = cm->IsSourceExtension(srcext); } // then put it accordingly into one of the two containers if (isCFile) { cFiles[s->GetFullPath()] = s; } else { otherFiles.insert(s->GetFullPath()); } } } default: // intended fallthrough break; } return projectType; } void cmExtraCodeLiteGenerator::CreateNewProjectFile( const std::vector& lgs, const std::string& filename) { const cmMakefile* mf = lgs[0]->GetMakefile(); cmGeneratedFileStream fout(filename.c_str()); if (!fout) { return; } cmXMLWriter xml(fout); //////////////////////////////////// xml.StartDocument("utf-8"); xml.StartElement("CodeLite_Project"); xml.Attribute("Name", lgs[0]->GetProjectName()); xml.Attribute("InternalType", ""); std::string projectType; // Collect all used source files in the project // Sort them into two containers, one for C/C++ implementation files // which may have an acompanying header, one for all other files std::map cFiles; std::set otherFiles; for (cmLocalGenerator* lg : lgs) { cmMakefile* makefile = lg->GetMakefile(); const std::vector& targets = lg->GetGeneratorTargets(); for (cmGeneratorTarget* target : targets) { projectType = CollectSourceFiles(makefile, target, cFiles, otherFiles); } } // Get the project path ( we need it later to convert files to // their relative path) std::string projectPath = cmSystemTools::GetFilenamePath(filename); CreateProjectSourceEntries(cFiles, otherFiles, &xml, projectPath, mf, projectType, ""); xml.EndElement(); // CodeLite_Project } void cmExtraCodeLiteGenerator::FindMatchingHeaderfiles( std::map& cFiles, std::set& otherFiles) { const std::vector& headerExts = this->GlobalGenerator->GetCMakeInstance()->GetHeaderExtensions(); // The following loop tries to add header files matching to implementation // files to the project. It does that by iterating over all source files, // replacing the file name extension with ".h" and checks whether such a // file exists. If it does, it is inserted into the map of files. // A very similar version of that code exists also in the kdevelop // project generator. for (auto const& sit : cFiles) { std::string headerBasename = cmSystemTools::GetFilenamePath(sit.first); headerBasename += "/"; headerBasename += cmSystemTools::GetFilenameWithoutExtension(sit.first); // check if there's a matching header around for (std::string const& ext : headerExts) { std::string hname = headerBasename; hname += "."; hname += ext; // if it's already in the set, don't check if it exists on disk std::set::const_iterator headerIt = otherFiles.find(hname); if (headerIt != otherFiles.end()) { break; } if (cmSystemTools::FileExists(hname.c_str())) { otherFiles.insert(hname); break; } } } } void cmExtraCodeLiteGenerator::CreateFoldersAndFiles( std::set& cFiles, cmXMLWriter& xml, const std::string& projectPath) { std::vector tmp_path; std::vector components; size_t numOfEndEl = 0; for (std::string const& cFile : cFiles) { std::string frelapath = cmSystemTools::RelativePath(projectPath.c_str(), cFile.c_str()); cmsys::SystemTools::SplitPath(frelapath, components, false); components.pop_back(); // erase last member -> it is file, not folder components.erase(components.begin()); // erase "root" size_t sizeOfSkip = 0; for (size_t i = 0; i < components.size(); ++i) { // skip relative path if (components[i] == ".." || components[i] == ".") { sizeOfSkip++; continue; } // same folder if (tmp_path.size() > i - sizeOfSkip && tmp_path[i - sizeOfSkip] == components[i]) { continue; } // delete "old" subfolders if (tmp_path.size() > i - sizeOfSkip) { numOfEndEl = tmp_path.size() - i + sizeOfSkip; tmp_path.erase(tmp_path.end() - numOfEndEl, tmp_path.end()); for (; numOfEndEl--;) { xml.EndElement(); } } // add folder xml.StartElement("VirtualDirectory"); xml.Attribute("Name", components[i]); tmp_path.push_back(components[i]); } // delete "old" subfolders numOfEndEl = tmp_path.size() - components.size() + sizeOfSkip; if (numOfEndEl) { tmp_path.erase(tmp_path.end() - numOfEndEl, tmp_path.end()); for (; numOfEndEl--;) { xml.EndElement(); } } // add file xml.StartElement("File"); xml.Attribute("Name", frelapath); xml.EndElement(); } // end of folders numOfEndEl = tmp_path.size(); for (; numOfEndEl--;) { xml.EndElement(); } } void cmExtraCodeLiteGenerator::CreateFoldersAndFiles( std::map& cFiles, cmXMLWriter& xml, const std::string& projectPath) { std::set s; for (auto const& it : cFiles) { s.insert(it.first); } this->CreateFoldersAndFiles(s, xml, projectPath); } void cmExtraCodeLiteGenerator::CreateProjectSourceEntries( std::map& cFiles, std::set& otherFiles, cmXMLWriter* _xml, const std::string& projectPath, const cmMakefile* mf, const std::string& projectType, const std::string& targetName) { cmXMLWriter& xml(*_xml); FindMatchingHeaderfiles(cFiles, otherFiles); // Create 2 virtual folders: src and include // and place all the implementation files into the src // folder, the rest goes to the include folder xml.StartElement("VirtualDirectory"); xml.Attribute("Name", "src"); // insert all source files in the codelite project // first the C/C++ implementation files, then all others this->CreateFoldersAndFiles(cFiles, xml, projectPath); xml.EndElement(); // VirtualDirectory xml.StartElement("VirtualDirectory"); xml.Attribute("Name", "include"); this->CreateFoldersAndFiles(otherFiles, xml, projectPath); xml.EndElement(); // VirtualDirectory // Get the number of CPUs. We use this information for the make -jN // command cmsys::SystemInformation info; info.RunCPUCheck(); this->CpuCount = info.GetNumberOfLogicalCPU() * info.GetNumberOfPhysicalCPU(); std::string codeliteCompilerName = this->GetCodeLiteCompilerName(mf); xml.StartElement("Settings"); xml.Attribute("Type", projectType); xml.StartElement("Configuration"); xml.Attribute("Name", this->ConfigName); xml.Attribute("CompilerType", this->GetCodeLiteCompilerName(mf)); xml.Attribute("DebuggerType", "GNU gdb debugger"); xml.Attribute("Type", projectType); xml.Attribute("BuildCmpWithGlobalSettings", "append"); xml.Attribute("BuildLnkWithGlobalSettings", "append"); xml.Attribute("BuildResWithGlobalSettings", "append"); xml.StartElement("Compiler"); xml.Attribute("Options", "-g"); xml.Attribute("Required", "yes"); xml.Attribute("PreCompiledHeader", ""); xml.StartElement("IncludePath"); xml.Attribute("Value", "."); xml.EndElement(); // IncludePath xml.EndElement(); // Compiler xml.StartElement("Linker"); xml.Attribute("Options", ""); xml.Attribute("Required", "yes"); xml.EndElement(); // Linker xml.StartElement("ResourceCompiler"); xml.Attribute("Options", ""); xml.Attribute("Required", "no"); xml.EndElement(); // ResourceCompiler xml.StartElement("General"); std::string outputPath = mf->GetSafeDefinition("EXECUTABLE_OUTPUT_PATH"); std::string relapath; if (!outputPath.empty()) { relapath = cmSystemTools::RelativePath(this->WorkspacePath.c_str(), outputPath.c_str()); xml.Attribute("OutputFile", relapath + "/$(ProjectName)"); } else { xml.Attribute("OutputFile", "$(IntermediateDirectory)/$(ProjectName)"); } xml.Attribute("IntermediateDirectory", "./"); xml.Attribute("Command", "./$(ProjectName)"); xml.Attribute("CommandArguments", ""); if (!outputPath.empty()) { xml.Attribute("WorkingDirectory", relapath); } else { xml.Attribute("WorkingDirectory", "$(IntermediateDirectory)"); } xml.Attribute("PauseExecWhenProcTerminates", "yes"); xml.EndElement(); // General xml.StartElement("Debugger"); xml.Attribute("IsRemote", "no"); xml.Attribute("RemoteHostName", ""); xml.Attribute("RemoteHostPort", ""); xml.Attribute("DebuggerPath", ""); xml.Element("PostConnectCommands"); xml.Element("StartupCommands"); xml.EndElement(); // Debugger xml.Element("PreBuild"); xml.Element("PostBuild"); xml.StartElement("CustomBuild"); xml.Attribute("Enabled", "yes"); xml.Element("RebuildCommand", GetRebuildCommand(mf, targetName)); xml.Element("CleanCommand", GetCleanCommand(mf, targetName)); xml.Element("BuildCommand", GetBuildCommand(mf, targetName)); xml.Element("SingleFileCommand", GetSingleFileBuildCommand(mf)); xml.Element("PreprocessFileCommand"); xml.Element("WorkingDirectory", "$(WorkspacePath)"); xml.EndElement(); // CustomBuild xml.StartElement("AdditionalRules"); xml.Element("CustomPostBuild"); xml.Element("CustomPreBuild"); xml.EndElement(); // AdditionalRules xml.EndElement(); // Configuration xml.StartElement("GlobalSettings"); xml.StartElement("Compiler"); xml.Attribute("Options", ""); xml.StartElement("IncludePath"); xml.Attribute("Value", "."); xml.EndElement(); // IncludePath xml.EndElement(); // Compiler xml.StartElement("Linker"); xml.Attribute("Options", ""); xml.StartElement("LibraryPath"); xml.Attribute("Value", "."); xml.EndElement(); // LibraryPath xml.EndElement(); // Linker xml.StartElement("ResourceCompiler"); xml.Attribute("Options", ""); xml.EndElement(); // ResourceCompiler xml.EndElement(); // GlobalSettings xml.EndElement(); // Settings } void cmExtraCodeLiteGenerator::CreateNewProjectFile( const cmGeneratorTarget* gt, const std::string& filename) { const cmMakefile* mf = gt->Makefile; cmGeneratedFileStream fout(filename.c_str()); if (!fout) { return; } cmXMLWriter xml(fout); //////////////////////////////////// xml.StartDocument("utf-8"); xml.StartElement("CodeLite_Project"); std::string targetName = gt->GetName(); std::string visualname = targetName; switch (gt->GetType()) { case cmStateEnums::STATIC_LIBRARY: case cmStateEnums::SHARED_LIBRARY: case cmStateEnums::MODULE_LIBRARY: visualname = "lib" + targetName; default: // intended fallthrough break; } xml.Attribute("Name", visualname); xml.Attribute("InternalType", ""); // Collect all used source files in the project // Sort them into two containers, one for C/C++ implementation files // which may have an acompanying header, one for all other files std::string projectType; std::map cFiles; std::set otherFiles; projectType = CollectSourceFiles(mf, gt, cFiles, otherFiles); // Get the project path ( we need it later to convert files to // their relative path) std::string projectPath = cmSystemTools::GetFilenamePath(filename); CreateProjectSourceEntries(cFiles, otherFiles, &xml, projectPath, mf, projectType, targetName); xml.EndElement(); // CodeLite_Project } std::string cmExtraCodeLiteGenerator::GetCodeLiteCompilerName( const cmMakefile* mf) const { // figure out which language to use // for now care only for C and C++ std::string compilerIdVar = "CMAKE_CXX_COMPILER_ID"; if (!this->GlobalGenerator->GetLanguageEnabled("CXX")) { compilerIdVar = "CMAKE_C_COMPILER_ID"; } std::string compilerId = mf->GetSafeDefinition(compilerIdVar); std::string compiler = "gnu g++"; // default to g++ // Since we need the compiler for parsing purposes only // it does not matter if we use clang or clang++, same as // "gnu gcc" vs "gnu g++" if (compilerId == "MSVC") { compiler = "VC++"; } else if (compilerId == "Clang") { compiler = "clang++"; } else if (compilerId == "GNU") { compiler = "gnu g++"; } return compiler; } std::string cmExtraCodeLiteGenerator::GetConfigurationName( const cmMakefile* mf) const { std::string confName = mf->GetSafeDefinition("CMAKE_BUILD_TYPE"); // Trim the configuration name from whitespaces (left and right) confName.erase(0, confName.find_first_not_of(" \t\r\v\n")); confName.erase(confName.find_last_not_of(" \t\r\v\n") + 1); if (confName.empty()) { confName = "NoConfig"; } return confName; } std::string cmExtraCodeLiteGenerator::GetBuildCommand( const cmMakefile* mf, const std::string& targetName) const { std::string generator = mf->GetSafeDefinition("CMAKE_GENERATOR"); std::string make = mf->GetRequiredDefinition("CMAKE_MAKE_PROGRAM"); std::string buildCommand = make; // Default std::ostringstream ss; if (generator == "NMake Makefiles" || generator == "Ninja") { ss << make; } else if (generator == "MinGW Makefiles" || generator == "Unix Makefiles") { ss << make << " -j " << this->CpuCount; } if (!targetName.empty()) { ss << " " << targetName; } buildCommand = ss.str(); return buildCommand; } std::string cmExtraCodeLiteGenerator::GetCleanCommand( const cmMakefile* mf, const std::string& targetName) const { std::string generator = mf->GetSafeDefinition("CMAKE_GENERATOR"); std::ostringstream ss; std::string buildcommand = GetBuildCommand(mf, ""); if (!targetName.empty() && generator == "Ninja") { ss << buildcommand << " -t clean " << targetName; } else { ss << buildcommand << " clean"; } return ss.str(); } std::string cmExtraCodeLiteGenerator::GetRebuildCommand( const cmMakefile* mf, const std::string& targetName) const { return GetCleanCommand(mf, targetName) + " && " + GetBuildCommand(mf, targetName); } std::string cmExtraCodeLiteGenerator::GetSingleFileBuildCommand( const cmMakefile* mf) const { std::string buildCommand; std::string make = mf->GetRequiredDefinition("CMAKE_MAKE_PROGRAM"); std::string generator = mf->GetSafeDefinition("CMAKE_GENERATOR"); if (generator == "Unix Makefiles" || generator == "MinGW Makefiles") { std::ostringstream ss; ss << make << " -f$(ProjectPath)/Makefile $(CurrentFileName).cpp.o"; buildCommand = ss.str(); } return buildCommand; }