/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmExtraEclipseCDT4Generator.h" #include #include #include #include #include #include #include #include "cmsys/RegularExpression.hxx" #include "cmGeneratedFileStream.h" #include "cmGeneratorExpression.h" #include "cmGeneratorTarget.h" #include "cmGlobalGenerator.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" #include "cmMessageType.h" #include "cmSourceFile.h" #include "cmSourceGroup.h" #include "cmState.h" #include "cmStateTypes.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmValue.h" #include "cmXMLWriter.h" #include "cmake.h" static void AppendAttribute(cmXMLWriter& xml, const char* keyval) { xml.StartElement("attribute"); xml.Attribute("key", keyval); xml.Attribute("value", keyval); xml.EndElement(); } template void AppendDictionary(cmXMLWriter& xml, const char* key, T const& value) { xml.StartElement("dictionary"); xml.Element("key", key); xml.Element("value", value); xml.EndElement(); } cmExtraEclipseCDT4Generator::cmExtraEclipseCDT4Generator() { this->IsOutOfSourceBuild = false; this->GenerateSourceProject = false; this->SupportsVirtualFolders = true; this->GenerateLinkedResources = true; this->SupportsGmakeErrorParser = true; this->SupportsMachO64Parser = true; this->CEnabled = false; this->CXXEnabled = false; } cmExternalMakefileProjectGeneratorFactory* cmExtraEclipseCDT4Generator::GetFactory() { static cmExternalMakefileProjectGeneratorSimpleFactory< cmExtraEclipseCDT4Generator> factory("Eclipse CDT4", "Generates Eclipse CDT 4.0 project files."); if (factory.GetSupportedGlobalGenerators().empty()) { // TODO: Verify if __CYGWIN__ should be checked. // #if defined(_WIN32) && !defined(__CYGWIN__) #if defined(_WIN32) factory.AddSupportedGlobalGenerator("NMake Makefiles"); factory.AddSupportedGlobalGenerator("MinGW Makefiles"); // factory.AddSupportedGlobalGenerator("MSYS Makefiles"); #endif factory.AddSupportedGlobalGenerator("Ninja"); factory.AddSupportedGlobalGenerator("Unix Makefiles"); } return &factory; } void cmExtraEclipseCDT4Generator::EnableLanguage( std::vector const& languages, cmMakefile* /*unused*/, bool /*optional*/) { for (std::string const& l : languages) { if (l == "CXX") { this->Natures.insert("org.eclipse.cdt.core.ccnature"); this->Natures.insert("org.eclipse.cdt.core.cnature"); this->CXXEnabled = true; } else if (l == "C") { this->Natures.insert("org.eclipse.cdt.core.cnature"); this->CEnabled = true; } else if (l == "Java") { this->Natures.insert("org.eclipse.jdt.core.javanature"); } } } void cmExtraEclipseCDT4Generator::Generate() { const auto& lg = this->GlobalGenerator->GetLocalGenerators()[0]; const cmMakefile* mf = lg->GetMakefile(); std::string eclipseVersion = mf->GetSafeDefinition("CMAKE_ECLIPSE_VERSION"); cmsys::RegularExpression regex(".*([0-9]+\\.[0-9]+).*"); if (regex.find(eclipseVersion)) { unsigned int majorVersion = 0; unsigned int minorVersion = 0; int res = sscanf(regex.match(1).c_str(), "%u.%u", &majorVersion, &minorVersion); if (res == 2) { int version = majorVersion * 1000 + minorVersion; if (version < 3006) // 3.6 is Helios { this->SupportsVirtualFolders = false; this->SupportsMachO64Parser = false; } if (version < 3007) // 3.7 is Indigo { this->SupportsGmakeErrorParser = false; } } } // TODO: Decide if these are local or member variables this->HomeDirectory = lg->GetSourceDirectory(); this->HomeOutputDirectory = lg->GetBinaryDirectory(); this->GenerateLinkedResources = mf->IsOn("CMAKE_ECLIPSE_GENERATE_LINKED_RESOURCES"); this->IsOutOfSourceBuild = (this->HomeDirectory != this->HomeOutputDirectory); this->GenerateSourceProject = (this->IsOutOfSourceBuild && mf->IsOn("CMAKE_ECLIPSE_GENERATE_SOURCE_PROJECT")); if (!this->GenerateSourceProject && (mf->IsOn("ECLIPSE_CDT4_GENERATE_SOURCE_PROJECT"))) { mf->IssueMessage( MessageType::WARNING, "ECLIPSE_CDT4_GENERATE_SOURCE_PROJECT is set to TRUE, " "but this variable is not supported anymore since CMake 2.8.7.\n" "Enable CMAKE_ECLIPSE_GENERATE_SOURCE_PROJECT instead."); } if (cmSystemTools::IsSubDirectory(this->HomeOutputDirectory, this->HomeDirectory)) { mf->IssueMessage(MessageType::WARNING, "The build directory is a subdirectory " "of the source directory.\n" "This is not supported well by Eclipse. It is strongly " "recommended to use a build directory which is a " "sibling of the source directory."); } // NOTE: This is not good, since it pollutes the source tree. However, // Eclipse doesn't allow CVS/SVN to work when the .project is not in // the cvs/svn root directory. Hence, this is provided as an option. if (this->GenerateSourceProject) { // create .project file in the source tree this->CreateSourceProjectFile(); } // create a .project file this->CreateProjectFile(); // create a .cproject file this->CreateCProjectFile(); // create resource settings this->CreateSettingsResourcePrefsFile(); } void cmExtraEclipseCDT4Generator::CreateSettingsResourcePrefsFile() { const auto& lg = this->GlobalGenerator->GetLocalGenerators()[0]; cmMakefile* mf = lg->GetMakefile(); const std::string filename = this->HomeOutputDirectory + "/.settings/org.eclipse.core.resources.prefs"; cmGeneratedFileStream fout(filename); if (!fout) { return; } fout << "eclipse.preferences.version=1\n"; cmValue encoding = mf->GetDefinition("CMAKE_ECLIPSE_RESOURCE_ENCODING"); if (encoding) { fout << "encoding/=" << *encoding << '\n'; } } void cmExtraEclipseCDT4Generator::CreateSourceProjectFile() { assert(this->HomeDirectory != this->HomeOutputDirectory); // set up the project name: -Source@ const auto& lg = this->GlobalGenerator->GetLocalGenerators()[0]; std::string name = cmExtraEclipseCDT4Generator::GenerateProjectName( lg->GetProjectName(), "Source", cmExtraEclipseCDT4Generator::GetPathBasename(this->HomeDirectory)); const std::string filename = this->HomeDirectory + "/.project"; cmGeneratedFileStream fout(filename); if (!fout) { return; } cmXMLWriter xml(fout); xml.StartDocument("UTF-8"); xml.StartElement("projectDescription"); xml.Element("name", name); xml.Element("comment", ""); xml.Element("projects", ""); xml.Element("buildSpec", ""); xml.Element("natures", ""); xml.StartElement("linkedResources"); if (this->SupportsVirtualFolders) { this->CreateLinksToSubprojects(xml, this->HomeDirectory); this->SrcLinkedResources.clear(); } xml.EndElement(); // linkedResources xml.EndElement(); // projectDescription xml.EndDocument(); } void cmExtraEclipseCDT4Generator::AddEnvVar(std::ostream& out, const char* envVar, cmLocalGenerator& lg) { cmMakefile* mf = lg.GetMakefile(); // get the variables from the environment and from the cache and then // figure out which one to use: std::string envVarValue; const bool envVarSet = cmSystemTools::GetEnv(envVar, envVarValue); std::string cacheEntryName = cmStrCat("CMAKE_ECLIPSE_ENVVAR_", envVar); cmValue cacheValue = lg.GetState()->GetInitializedCacheValue(cacheEntryName); // now we have both, decide which one to use std::string valueToUse; if (!envVarSet && !cacheValue) { // nothing known, do nothing valueToUse.clear(); } else if (envVarSet && !cacheValue) { // The variable is in the env, but not in the cache. Use it and put it // in the cache valueToUse = envVarValue; mf->AddCacheDefinition(cacheEntryName, valueToUse, cacheEntryName.c_str(), cmStateEnums::STRING, true); mf->GetCMakeInstance()->SaveCache(lg.GetBinaryDirectory()); } else if (!envVarSet && cacheValue) { // It is already in the cache, but not in the env, so use it from the cache valueToUse = *cacheValue; } else { // It is both in the cache and in the env. // Use the version from the env. except if the value from the env is // completely contained in the value from the cache (for the case that we // now have a PATH without MSVC dirs in the env. but had the full PATH with // all MSVC dirs during the cmake run which stored the var in the cache: valueToUse = *cacheValue; if (valueToUse.find(envVarValue) == std::string::npos) { valueToUse = envVarValue; mf->AddCacheDefinition(cacheEntryName, valueToUse, cacheEntryName.c_str(), cmStateEnums::STRING, true); mf->GetCMakeInstance()->SaveCache(lg.GetBinaryDirectory()); } } if (!valueToUse.empty()) { out << envVar << "=" << valueToUse << "|"; } } void cmExtraEclipseCDT4Generator::CreateProjectFile() { const auto& lg = this->GlobalGenerator->GetLocalGenerators()[0]; cmMakefile* mf = lg->GetMakefile(); const std::string filename = this->HomeOutputDirectory + "/.project"; cmGeneratedFileStream fout(filename); if (!fout) { return; } std::string compilerId = mf->GetSafeDefinition("CMAKE_C_COMPILER_ID"); if (compilerId.empty()) // no C compiler, try the C++ compiler: { compilerId = mf->GetSafeDefinition("CMAKE_CXX_COMPILER_ID"); } cmXMLWriter xml(fout); xml.StartDocument("UTF-8"); xml.StartElement("projectDescription"); xml.Element("name", cmExtraEclipseCDT4Generator::GenerateProjectName( lg->GetProjectName(), mf->GetSafeDefinition("CMAKE_BUILD_TYPE"), cmExtraEclipseCDT4Generator::GetPathBasename( this->HomeOutputDirectory))); xml.Element("comment", ""); xml.Element("projects", ""); xml.StartElement("buildSpec"); xml.StartElement("buildCommand"); xml.Element("name", "org.eclipse.cdt.make.core.makeBuilder"); xml.Element("triggers", "clean,full,incremental,"); xml.StartElement("arguments"); // use clean target AppendDictionary(xml, "org.eclipse.cdt.make.core.cleanBuildTarget", "clean"); AppendDictionary(xml, "org.eclipse.cdt.make.core.enableCleanBuild", "true"); AppendDictionary(xml, "org.eclipse.cdt.make.core.append_environment", "true"); AppendDictionary(xml, "org.eclipse.cdt.make.core.stopOnError", "true"); // set the make command AppendDictionary(xml, "org.eclipse.cdt.make.core.enabledIncrementalBuild", "true"); AppendDictionary(xml, "org.eclipse.cdt.make.core.build.command", cmExtraEclipseCDT4Generator::GetEclipsePath( mf->GetRequiredDefinition("CMAKE_MAKE_PROGRAM"))); AppendDictionary(xml, "org.eclipse.cdt.make.core.contents", "org.eclipse.cdt.make.core.activeConfigSettings"); AppendDictionary(xml, "org.eclipse.cdt.make.core.build.target.inc", "all"); AppendDictionary(xml, "org.eclipse.cdt.make.core.build.arguments", mf->GetSafeDefinition("CMAKE_ECLIPSE_MAKE_ARGUMENTS")); AppendDictionary( xml, "org.eclipse.cdt.make.core.buildLocation", cmExtraEclipseCDT4Generator::GetEclipsePath(this->HomeOutputDirectory)); AppendDictionary(xml, "org.eclipse.cdt.make.core.useDefaultBuildCmd", "false"); // set project specific environment std::ostringstream environment; environment << "VERBOSE=1|CMAKE_NO_VERBOSE=1|"; // verbose Makefile output // set vsvars32.bat environment available at CMake time, // but not necessarily when eclipse is open if (compilerId == "MSVC") { AddEnvVar(environment, "PATH", *lg); AddEnvVar(environment, "INCLUDE", *lg); AddEnvVar(environment, "LIB", *lg); AddEnvVar(environment, "LIBPATH", *lg); } else if (compilerId == "Intel") { // if the env.var is set, use this one and put it in the cache // if the env.var is not set, but the value is in the cache, // use it from the cache: AddEnvVar(environment, "INTEL_LICENSE_FILE", *lg); } AppendDictionary(xml, "org.eclipse.cdt.make.core.environment", environment.str()); AppendDictionary(xml, "org.eclipse.cdt.make.core.enableFullBuild", "true"); AppendDictionary(xml, "org.eclipse.cdt.make.core.build.target.auto", "all"); AppendDictionary(xml, "org.eclipse.cdt.make.core.enableAutoBuild", "false"); AppendDictionary(xml, "org.eclipse.cdt.make.core.build.target.clean", "clean"); AppendDictionary(xml, "org.eclipse.cdt.make.core.fullBuildTarget", "all"); AppendDictionary(xml, "org.eclipse.cdt.make.core.buildArguments", ""); AppendDictionary( xml, "org.eclipse.cdt.make.core.build.location", cmExtraEclipseCDT4Generator::GetEclipsePath(this->HomeOutputDirectory)); AppendDictionary(xml, "org.eclipse.cdt.make.core.autoBuildTarget", "all"); // set error parsers std::ostringstream errorOutputParser; if (compilerId == "MSVC") { errorOutputParser << "org.eclipse.cdt.core.VCErrorParser;"; } else if (compilerId == "Intel") { errorOutputParser << "org.eclipse.cdt.core.ICCErrorParser;"; } if (this->SupportsGmakeErrorParser) { errorOutputParser << "org.eclipse.cdt.core.GmakeErrorParser;"; } else { errorOutputParser << "org.eclipse.cdt.core.MakeErrorParser;"; } errorOutputParser << "org.eclipse.cdt.core.GCCErrorParser;" "org.eclipse.cdt.core.GASErrorParser;" "org.eclipse.cdt.core.GLDErrorParser;"; AppendDictionary(xml, "org.eclipse.cdt.core.errorOutputParser", errorOutputParser.str()); xml.EndElement(); // arguments xml.EndElement(); // buildCommand xml.StartElement("buildCommand"); xml.Element("name", "org.eclipse.cdt.make.core.ScannerConfigBuilder"); xml.StartElement("arguments"); xml.EndElement(); // arguments xml.EndElement(); // buildCommand xml.EndElement(); // buildSpec // set natures for c/c++ projects xml.StartElement("natures"); xml.Element("nature", "org.eclipse.cdt.make.core.makeNature"); xml.Element("nature", "org.eclipse.cdt.make.core.ScannerConfigNature"); for (std::string const& n : this->Natures) { xml.Element("nature", n); } if (cmValue extraNaturesProp = mf->GetState()->GetGlobalProperty("ECLIPSE_EXTRA_NATURES")) { std::vector extraNatures = cmExpandedList(*extraNaturesProp); for (std::string const& n : extraNatures) { xml.Element("nature", n); } } xml.EndElement(); // natures xml.StartElement("linkedResources"); // create linked resources if (this->IsOutOfSourceBuild) { // create a linked resource to CMAKE_SOURCE_DIR // (this is not done anymore for each project because of // https://gitlab.kitware.com/cmake/cmake/-/issues/9978 and because I found // it actually quite confusing in bigger projects with many directories and // projects, Alex std::string sourceLinkedResourceName = "[Source directory]"; std::string linkSourceDirectory = cmExtraEclipseCDT4Generator::GetEclipsePath( lg->GetCurrentSourceDirectory()); // .project dir can't be subdir of a linked resource dir if (!cmSystemTools::IsSubDirectory(this->HomeOutputDirectory, linkSourceDirectory)) { cmExtraEclipseCDT4Generator::AppendLinkedResource( xml, sourceLinkedResourceName, cmExtraEclipseCDT4Generator::GetEclipsePath(linkSourceDirectory), LinkToFolder); this->SrcLinkedResources.push_back(std::move(sourceLinkedResourceName)); } } if (this->SupportsVirtualFolders) { this->CreateLinksToSubprojects(xml, this->HomeOutputDirectory); this->CreateLinksForTargets(xml); } xml.EndElement(); // linkedResources xml.EndElement(); // projectDescription } void cmExtraEclipseCDT4Generator::WriteGroups( std::vector const& sourceGroups, std::string& linkName, cmXMLWriter& xml) { for (cmSourceGroup const& sg : sourceGroups) { std::string linkName3 = cmStrCat(linkName, '/', sg.GetFullName()); std::replace(linkName3.begin(), linkName3.end(), '\\', '/'); cmExtraEclipseCDT4Generator::AppendLinkedResource( xml, linkName3, "virtual:/virtual", VirtualFolder); std::vector const& children = sg.GetGroupChildren(); if (!children.empty()) { this->WriteGroups(children, linkName, xml); } std::vector sFiles = sg.GetSourceFiles(); for (cmSourceFile const* file : sFiles) { std::string const& fullPath = file->GetFullPath(); if (!cmSystemTools::FileIsDirectory(fullPath)) { std::string linkName4 = cmStrCat(linkName3, '/', cmSystemTools::GetFilenameName(fullPath)); cmExtraEclipseCDT4Generator::AppendLinkedResource( xml, linkName4, cmExtraEclipseCDT4Generator::GetEclipsePath(fullPath), LinkToFile); } } } } void cmExtraEclipseCDT4Generator::CreateLinksForTargets(cmXMLWriter& xml) { std::string linkName = "[Targets]"; cmExtraEclipseCDT4Generator::AppendLinkedResource( xml, linkName, "virtual:/virtual", VirtualFolder); for (const auto& lg : this->GlobalGenerator->GetLocalGenerators()) { cmMakefile* makefile = lg->GetMakefile(); const auto& targets = lg->GetGeneratorTargets(); for (const auto& target : targets) { std::string linkName2 = cmStrCat(linkName, '/'); switch (target->GetType()) { case cmStateEnums::EXECUTABLE: case cmStateEnums::STATIC_LIBRARY: case cmStateEnums::SHARED_LIBRARY: case cmStateEnums::MODULE_LIBRARY: case cmStateEnums::OBJECT_LIBRARY: { const char* prefix = (target->GetType() == cmStateEnums::EXECUTABLE ? "[exe] " : "[lib] "); linkName2 += prefix; linkName2 += target->GetName(); cmExtraEclipseCDT4Generator::AppendLinkedResource( xml, linkName2, "virtual:/virtual", VirtualFolder); if (!this->GenerateLinkedResources) { break; // skip generating the linked resources to the source files } std::vector sourceGroups = makefile->GetSourceGroups(); // get the files from the source lists then add them to the groups std::vector files; target->GetSourceFiles( files, makefile->GetSafeDefinition("CMAKE_BUILD_TYPE")); for (cmSourceFile* sf : files) { // Add the file to the list of sources. std::string const& source = sf->ResolveFullPath(); cmSourceGroup* sourceGroup = makefile->FindSourceGroup(source, sourceGroups); sourceGroup->AssignSource(sf); } this->WriteGroups(sourceGroups, linkName2, xml); } break; // ignore all others: default: break; } } } } void cmExtraEclipseCDT4Generator::CreateLinksToSubprojects( cmXMLWriter& xml, const std::string& baseDir) { if (!this->GenerateLinkedResources) { return; } // for each sub project create a linked resource to the source dir // - only if it is an out-of-source build cmExtraEclipseCDT4Generator::AppendLinkedResource( xml, "[Subprojects]", "virtual:/virtual", VirtualFolder); for (auto const& it : this->GlobalGenerator->GetProjectMap()) { std::string linkSourceDirectory = cmExtraEclipseCDT4Generator::GetEclipsePath( it.second[0]->GetCurrentSourceDirectory()); // a linked resource must not point to a parent directory of .project or // .project itself if ((baseDir != linkSourceDirectory) && !cmSystemTools::IsSubDirectory(baseDir, linkSourceDirectory)) { std::string linkName = cmStrCat("[Subprojects]/", it.first); cmExtraEclipseCDT4Generator::AppendLinkedResource( xml, linkName, cmExtraEclipseCDT4Generator::GetEclipsePath(linkSourceDirectory), LinkToFolder); // Don't add it to the srcLinkedResources, because listing multiple // directories confuses the Eclipse indexer (#13596). } } } void cmExtraEclipseCDT4Generator::AppendIncludeDirectories( cmXMLWriter& xml, const std::vector& includeDirs, std::set& emittedDirs) { for (std::string const& inc : includeDirs) { if (!inc.empty()) { std::string dir = cmSystemTools::CollapseFullPath(inc); // handle framework include dirs on OSX, the remainder after the // Frameworks/ part has to be stripped // /System/Library/Frameworks/GLUT.framework/Headers cmsys::RegularExpression frameworkRx("(.+/Frameworks)/.+\\.framework/"); if (frameworkRx.find(dir)) { dir = frameworkRx.match(1); } if (emittedDirs.find(dir) == emittedDirs.end()) { emittedDirs.insert(dir); xml.StartElement("pathentry"); xml.Attribute("include", cmExtraEclipseCDT4Generator::GetEclipsePath(dir)); xml.Attribute("kind", "inc"); xml.Attribute("path", ""); xml.Attribute("system", "true"); xml.EndElement(); } } } } void cmExtraEclipseCDT4Generator::CreateCProjectFile() const { std::set emitted; const auto& lg = this->GlobalGenerator->GetLocalGenerators()[0]; const cmMakefile* mf = lg->GetMakefile(); const std::string filename = this->HomeOutputDirectory + "/.cproject"; cmGeneratedFileStream fout(filename); if (!fout) { return; } cmXMLWriter xml(fout); // add header xml.StartDocument("UTF-8"); xml.ProcessingInstruction("fileVersion", "4.0.0"); xml.StartElement("cproject"); xml.StartElement("storageModule"); xml.Attribute("moduleId", "org.eclipse.cdt.core.settings"); xml.StartElement("cconfiguration"); xml.Attribute("id", "org.eclipse.cdt.core.default.config.1"); // Configuration settings... xml.StartElement("storageModule"); xml.Attribute("buildSystemId", "org.eclipse.cdt.core.defaultConfigDataProvider"); xml.Attribute("id", "org.eclipse.cdt.core.default.config.1"); xml.Attribute("moduleId", "org.eclipse.cdt.core.settings"); xml.Attribute("name", "Configuration"); xml.Element("externalSettings"); xml.StartElement("extensions"); // TODO: refactor this out... std::string executableFormat = mf->GetSafeDefinition("CMAKE_EXECUTABLE_FORMAT"); if (executableFormat == "ELF") { xml.StartElement("extension"); xml.Attribute("id", "org.eclipse.cdt.core.ELF"); xml.Attribute("point", "org.eclipse.cdt.core.BinaryParser"); xml.EndElement(); // extension xml.StartElement("extension"); xml.Attribute("id", "org.eclipse.cdt.core.GNU_ELF"); xml.Attribute("point", "org.eclipse.cdt.core.BinaryParser"); AppendAttribute(xml, "addr2line"); AppendAttribute(xml, "c++filt"); xml.EndElement(); // extension } else { std::string systemName = mf->GetSafeDefinition("CMAKE_SYSTEM_NAME"); if (systemName == "CYGWIN" || systemName == "MSYS") { xml.StartElement("extension"); xml.Attribute("id", "org.eclipse.cdt.core.Cygwin_PE"); xml.Attribute("point", "org.eclipse.cdt.core.BinaryParser"); AppendAttribute(xml, "addr2line"); AppendAttribute(xml, "c++filt"); AppendAttribute(xml, "cygpath"); AppendAttribute(xml, "nm"); xml.EndElement(); // extension } else if (systemName == "Windows") { xml.StartElement("extension"); xml.Attribute("id", "org.eclipse.cdt.core.PE"); xml.Attribute("point", "org.eclipse.cdt.core.BinaryParser"); xml.EndElement(); // extension } else if (systemName == "Darwin") { xml.StartElement("extension"); xml.Attribute("id", this->SupportsMachO64Parser ? "org.eclipse.cdt.core.MachO64" : "org.eclipse.cdt.core.MachO"); xml.Attribute("point", "org.eclipse.cdt.core.BinaryParser"); AppendAttribute(xml, "c++filt"); xml.EndElement(); // extension } else { // *** Should never get here *** xml.Element("error_toolchain_type"); } } xml.EndElement(); // extensions xml.EndElement(); // storageModule // ??? xml.StartElement("storageModule"); xml.Attribute("moduleId", "org.eclipse.cdt.core.language.mapping"); xml.Element("project-mappings"); xml.EndElement(); // storageModule // ??? xml.StartElement("storageModule"); xml.Attribute("moduleId", "org.eclipse.cdt.core.externalSettings"); xml.EndElement(); // storageModule // set the path entries (includes, libs, source dirs, etc.) xml.StartElement("storageModule"); xml.Attribute("moduleId", "org.eclipse.cdt.core.pathentry"); // for each sub project with a linked resource to the source dir: // - make it type 'src' // - and exclude it from type 'out' std::string excludeFromOut; /* I don't know what the pathentry kind="src" are good for, e.g. * autocompletion * works also without them. Done wrong, the indexer complains, see #12417 * and #12213. * According to #13596, this entry at least limits the directories the * indexer is searching for files. So now the "src" entry contains only * the linked resource to CMAKE_SOURCE_DIR. * The CDT documentation is very terse on that: * "CDT_SOURCE: Entry kind constant describing a path entry identifying a * folder containing source code to be compiled." * Also on the cdt-dev list didn't bring any information: * http://web.archiveorange.com/archive/v/B4NlJDNIpYoOS1SbxFNy * Alex */ // include subprojects directory to the src pathentry // eclipse cdt indexer uses this entries as reference to index source files if (this->GenerateLinkedResources) { xml.StartElement("pathentry"); xml.Attribute("kind", "src"); xml.Attribute("path", "[Subprojects]"); xml.EndElement(); } for (std::string const& p : this->SrcLinkedResources) { xml.StartElement("pathentry"); xml.Attribute("kind", "src"); xml.Attribute("path", p); xml.EndElement(); // exclude source directory from output search path // - only if not named the same as an output directory if (!cmSystemTools::FileIsDirectory( cmStrCat(this->HomeOutputDirectory, '/', p))) { excludeFromOut += p + "/|"; } } excludeFromOut += "**/CMakeFiles/"; xml.StartElement("pathentry"); xml.Attribute("excluding", excludeFromOut); xml.Attribute("kind", "out"); xml.Attribute("path", ""); xml.EndElement(); // add pre-processor definitions to allow eclipse to gray out sections emitted.clear(); for (const auto& lgen : this->GlobalGenerator->GetLocalGenerators()) { if (cmValue cdefs = lgen->GetMakefile()->GetProperty("COMPILE_DEFINITIONS")) { // Expand the list. std::vector defs; cmGeneratorExpression::Split(*cdefs, defs); for (std::string const& d : defs) { if (cmGeneratorExpression::Find(d) != std::string::npos) { continue; } std::string::size_type equals = d.find('=', 0); std::string::size_type enddef = d.length(); std::string def; std::string val; if (equals != std::string::npos && equals < enddef) { // we have -DFOO=BAR def = d.substr(0, equals); val = d.substr(equals + 1, enddef - equals + 1); } else { // we have -DFOO def = d; } // insert the definition if not already added. if (emitted.find(def) == emitted.end()) { emitted.insert(def); xml.StartElement("pathentry"); xml.Attribute("kind", "mac"); xml.Attribute("name", def); xml.Attribute("path", ""); xml.Attribute("value", val); xml.EndElement(); } } } } // add system defined c macros cmValue cDefs = mf->GetDefinition("CMAKE_EXTRA_GENERATOR_C_SYSTEM_DEFINED_MACROS"); if (this->CEnabled && cDefs) { // Expand the list. std::vector defs = cmExpandedList(*cDefs, true); // the list must contain only definition-value pairs: if ((defs.size() % 2) == 0) { auto di = defs.begin(); while (di != defs.end()) { std::string def = *di; ++di; std::string val; if (di != defs.end()) { val = *di; ++di; } // insert the definition if not already added. if (emitted.find(def) == emitted.end()) { emitted.insert(def); xml.StartElement("pathentry"); xml.Attribute("kind", "mac"); xml.Attribute("name", def); xml.Attribute("path", ""); xml.Attribute("value", val); xml.EndElement(); } } } } // add system defined c++ macros cmValue cxxDefs = mf->GetDefinition("CMAKE_EXTRA_GENERATOR_CXX_SYSTEM_DEFINED_MACROS"); if (this->CXXEnabled && cxxDefs) { // Expand the list. std::vector defs = cmExpandedList(*cxxDefs, true); // the list must contain only definition-value pairs: if ((defs.size() % 2) == 0) { auto di = defs.begin(); while (di != defs.end()) { std::string def = *di; ++di; std::string val; if (di != defs.end()) { val = *di; ++di; } // insert the definition if not already added. if (emitted.find(def) == emitted.end()) { emitted.insert(def); xml.StartElement("pathentry"); xml.Attribute("kind", "mac"); xml.Attribute("name", def); xml.Attribute("path", ""); xml.Attribute("value", val); xml.EndElement(); } } } } // include dirs emitted.clear(); for (const auto& lgen : this->GlobalGenerator->GetLocalGenerators()) { const auto& targets = lgen->GetGeneratorTargets(); for (const auto& target : targets) { if (target->GetType() == cmStateEnums::INTERFACE_LIBRARY) { continue; } std::vector includeDirs; std::string config = mf->GetSafeDefinition("CMAKE_BUILD_TYPE"); lgen->GetIncludeDirectories(includeDirs, target.get(), "C", config); this->AppendIncludeDirectories(xml, includeDirs, emitted); } } // now also the system include directories, in case we found them in // CMakeSystemSpecificInformation.cmake. This makes Eclipse find the // standard headers. std::string compiler = mf->GetSafeDefinition("CMAKE_C_COMPILER"); if (this->CEnabled && !compiler.empty()) { std::string systemIncludeDirs = mf->GetSafeDefinition("CMAKE_EXTRA_GENERATOR_C_SYSTEM_INCLUDE_DIRS"); std::vector dirs = cmExpandedList(systemIncludeDirs); this->AppendIncludeDirectories(xml, dirs, emitted); } compiler = mf->GetSafeDefinition("CMAKE_CXX_COMPILER"); if (this->CXXEnabled && !compiler.empty()) { std::string systemIncludeDirs = mf->GetSafeDefinition("CMAKE_EXTRA_GENERATOR_CXX_SYSTEM_INCLUDE_DIRS"); std::vector dirs = cmExpandedList(systemIncludeDirs); this->AppendIncludeDirectories(xml, dirs, emitted); } xml.EndElement(); // storageModule // add build targets xml.StartElement("storageModule"); xml.Attribute("moduleId", "org.eclipse.cdt.make.core.buildtargets"); xml.StartElement("buildTargets"); emitted.clear(); const std::string& make = mf->GetRequiredDefinition("CMAKE_MAKE_PROGRAM"); const std::string& makeArgs = mf->GetSafeDefinition("CMAKE_ECLIPSE_MAKE_ARGUMENTS"); cmGlobalGenerator* generator = const_cast(this->GlobalGenerator); std::string allTarget; std::string cleanTarget; if (generator->GetAllTargetName()) { allTarget = generator->GetAllTargetName(); } if (generator->GetCleanTargetName()) { cleanTarget = generator->GetCleanTargetName(); } // add all executable and library targets and some of the GLOBAL // and UTILITY targets for (const auto& lgen : this->GlobalGenerator->GetLocalGenerators()) { const auto& targets = lgen->GetGeneratorTargets(); std::string subdir = lgen->MaybeRelativeToTopBinDir(lgen->GetCurrentBinaryDirectory()); if (subdir == ".") { subdir.clear(); } for (const auto& target : targets) { std::string targetName = target->GetName(); switch (target->GetType()) { case cmStateEnums::GLOBAL_TARGET: { // Only add the global targets from CMAKE_BINARY_DIR, // not from the subdirs if (subdir.empty()) { cmExtraEclipseCDT4Generator::AppendTarget(xml, targetName, make, makeArgs, subdir, ": "); } } break; case cmStateEnums::UTILITY: // Add all utility targets, except the Nightly/Continuous/ // Experimental-"sub"targets as e.g. NightlyStart if ((cmHasLiteralPrefix(targetName, "Nightly") && (targetName != "Nightly")) || (cmHasLiteralPrefix(targetName, "Continuous") && (targetName != "Continuous")) || (cmHasLiteralPrefix(targetName, "Experimental") && (targetName != "Experimental"))) { break; } cmExtraEclipseCDT4Generator::AppendTarget(xml, targetName, make, makeArgs, subdir, ": "); break; case cmStateEnums::EXECUTABLE: case cmStateEnums::STATIC_LIBRARY: case cmStateEnums::SHARED_LIBRARY: case cmStateEnums::MODULE_LIBRARY: case cmStateEnums::OBJECT_LIBRARY: { const char* prefix = (target->GetType() == cmStateEnums::EXECUTABLE ? "[exe] " : "[lib] "); cmExtraEclipseCDT4Generator::AppendTarget(xml, targetName, make, makeArgs, subdir, prefix); std::string fastTarget = cmStrCat(targetName, "/fast"); cmExtraEclipseCDT4Generator::AppendTarget(xml, fastTarget, make, makeArgs, subdir, prefix); // Add Build and Clean targets in the virtual folder of targets: if (this->SupportsVirtualFolders) { std::string virtDir = cmStrCat("[Targets]/", prefix, targetName); std::string buildArgs = cmStrCat("-C \"", lgen->GetBinaryDirectory(), "\" ", makeArgs); cmExtraEclipseCDT4Generator::AppendTarget( xml, "Build", make, buildArgs, virtDir, "", targetName.c_str()); std::string cleanArgs = cmStrCat("-E chdir \"", lgen->GetCurrentBinaryDirectory(), "\" \"", cmSystemTools::GetCMakeCommand(), "\" -P \""); cleanArgs += lgen->GetTargetDirectory(target.get()); cleanArgs += "/cmake_clean.cmake\""; cmExtraEclipseCDT4Generator::AppendTarget( xml, "Clean", cmSystemTools::GetCMakeCommand(), cleanArgs, virtDir, "", ""); } } break; case cmStateEnums::INTERFACE_LIBRARY: default: break; } } // insert the all and clean targets in every subdir if (!allTarget.empty()) { cmExtraEclipseCDT4Generator::AppendTarget(xml, allTarget, make, makeArgs, subdir, ": "); } if (!cleanTarget.empty()) { cmExtraEclipseCDT4Generator::AppendTarget(xml, cleanTarget, make, makeArgs, subdir, ": "); } // insert rules for compiling, preprocessing and assembling individual // files std::vector objectFileTargets; lg->GetIndividualFileTargets(objectFileTargets); for (std::string const& f : objectFileTargets) { const char* prefix = "[obj] "; if (f.back() == 's') { prefix = "[to asm] "; } else if (f.back() == 'i') { prefix = "[pre] "; } cmExtraEclipseCDT4Generator::AppendTarget(xml, f, make, makeArgs, subdir, prefix); } } xml.EndElement(); // buildTargets xml.EndElement(); // storageModule cmExtraEclipseCDT4Generator::AppendStorageScanners(xml, *mf); xml.EndElement(); // cconfiguration xml.EndElement(); // storageModule xml.StartElement("storageModule"); xml.Attribute("moduleId", "cdtBuildSystem"); xml.Attribute("version", "4.0.0"); xml.StartElement("project"); xml.Attribute("id", std::string(lg->GetProjectName()) + ".null.1"); xml.Attribute("name", lg->GetProjectName()); xml.EndElement(); // project xml.EndElement(); // storageModule // Append additional cproject contents without applying any XML formatting if (cmValue extraCProjectContents = mf->GetState()->GetGlobalProperty("ECLIPSE_EXTRA_CPROJECT_CONTENTS")) { fout << *extraCProjectContents; } xml.EndElement(); // cproject } std::string cmExtraEclipseCDT4Generator::GetEclipsePath( const std::string& path) { #if defined(__CYGWIN__) std::string cmd = "cygpath -m " + path; std::string out; if (!cmSystemTools::RunSingleCommand(cmd.c_str(), &out, &out)) { return path; } else { out.erase(out.find_last_of('\n')); return out; } #else return path; #endif } std::string cmExtraEclipseCDT4Generator::GetPathBasename( const std::string& path) { std::string outputBasename = path; while (!outputBasename.empty() && (outputBasename.back() == '/' || outputBasename.back() == '\\')) { outputBasename.resize(outputBasename.size() - 1); } std::string::size_type loc = outputBasename.find_last_of("/\\"); if (loc != std::string::npos) { outputBasename = outputBasename.substr(loc + 1); } return outputBasename; } std::string cmExtraEclipseCDT4Generator::GenerateProjectName( const std::string& name, const std::string& type, const std::string& path) { return name + (type.empty() ? "" : "-") + type + "@" + path; } // Helper functions void cmExtraEclipseCDT4Generator::AppendStorageScanners( cmXMLWriter& xml, const cmMakefile& makefile) { // we need the "make" and the C (or C++) compiler which are used, Alex const std::string& make = makefile.GetRequiredDefinition("CMAKE_MAKE_PROGRAM"); std::string compiler = makefile.GetSafeDefinition("CMAKE_C_COMPILER"); std::string arg1 = makefile.GetSafeDefinition("CMAKE_C_COMPILER_ARG1"); if (compiler.empty()) { compiler = makefile.GetSafeDefinition("CMAKE_CXX_COMPILER"); arg1 = makefile.GetSafeDefinition("CMAKE_CXX_COMPILER_ARG1"); } if (compiler.empty()) // Hmm, what to do now ? { compiler = "gcc"; } // the following right now hardcodes gcc behavior :-/ std::string compilerArgs = "-E -P -v -dD ${plugin_state_location}/${specs_file}"; if (!arg1.empty()) { arg1 += " "; compilerArgs = arg1 + compilerArgs; } xml.StartElement("storageModule"); xml.Attribute("moduleId", "scannerConfiguration"); xml.StartElement("autodiscovery"); xml.Attribute("enabled", "true"); xml.Attribute("problemReportingEnabled", "true"); xml.Attribute("selectedProfileId", "org.eclipse.cdt.make.core.GCCStandardMakePerProjectProfile"); xml.EndElement(); // autodiscovery cmExtraEclipseCDT4Generator::AppendScannerProfile( xml, "org.eclipse.cdt.make.core.GCCStandardMakePerProjectProfile", true, "", true, "specsFile", compilerArgs, compiler, true, true); cmExtraEclipseCDT4Generator::AppendScannerProfile( xml, "org.eclipse.cdt.make.core.GCCStandardMakePerFileProfile", true, "", true, "makefileGenerator", "-f ${project_name}_scd.mk", make, true, true); xml.EndElement(); // storageModule } // The prefix is prepended before the actual name of the target. The purpose // of that is to sort the targets in the view of Eclipse, so that at first // the global/utility/all/clean targets appear ": ", then the executable // targets "[exe] ", then the libraries "[lib]", then the rules for the // object files "[obj]", then for preprocessing only "[pre] " and // finally the assembly files "[to asm] ". Note the "to" in "to asm", // without it, "asm" would be the first targets in the list, with the "to" // they are the last targets, which makes more sense. void cmExtraEclipseCDT4Generator::AppendTarget( cmXMLWriter& xml, const std::string& target, const std::string& make, const std::string& makeArgs, const std::string& path, const char* prefix, const char* makeTarget) { xml.StartElement("target"); xml.Attribute("name", prefix + target); xml.Attribute("path", path); xml.Attribute("targetID", "org.eclipse.cdt.make.MakeTargetBuilder"); xml.Element("buildCommand", cmExtraEclipseCDT4Generator::GetEclipsePath(make)); xml.Element("buildArguments", makeArgs); xml.Element("buildTarget", makeTarget ? makeTarget : target.c_str()); xml.Element("stopOnError", "true"); xml.Element("useDefaultCommand", "false"); xml.EndElement(); } void cmExtraEclipseCDT4Generator::AppendScannerProfile( cmXMLWriter& xml, const std::string& profileID, bool openActionEnabled, const std::string& openActionFilePath, bool pParserEnabled, const std::string& scannerInfoProviderID, const std::string& runActionArguments, const std::string& runActionCommand, bool runActionUseDefault, bool sipParserEnabled) { xml.StartElement("profile"); xml.Attribute("id", profileID); xml.StartElement("buildOutputProvider"); xml.StartElement("openAction"); xml.Attribute("enabled", openActionEnabled ? "true" : "false"); xml.Attribute("filePath", openActionFilePath); xml.EndElement(); // openAction xml.StartElement("parser"); xml.Attribute("enabled", pParserEnabled ? "true" : "false"); xml.EndElement(); // parser xml.EndElement(); // buildOutputProvider xml.StartElement("scannerInfoProvider"); xml.Attribute("id", scannerInfoProviderID); xml.StartElement("runAction"); xml.Attribute("arguments", runActionArguments); xml.Attribute("command", runActionCommand); xml.Attribute("useDefault", runActionUseDefault ? "true" : "false"); xml.EndElement(); // runAction xml.StartElement("parser"); xml.Attribute("enabled", sipParserEnabled ? "true" : "false"); xml.EndElement(); // parser xml.EndElement(); // scannerInfoProvider xml.EndElement(); // profile } void cmExtraEclipseCDT4Generator::AppendLinkedResource(cmXMLWriter& xml, const std::string& name, const std::string& path, LinkType linkType) { const char* locationTag = "location"; int typeTag = 2; if (linkType == VirtualFolder) // ... and not a linked folder { locationTag = "locationURI"; } if (linkType == LinkToFile) { typeTag = 1; } xml.StartElement("link"); xml.Element("name", name); xml.Element("type", typeTag); xml.Element(locationTag, path); xml.EndElement(); }