diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 892a7d2e5f..f9066b3bed 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -975,6 +975,8 @@ if(WIN32) cmVisualStudioWCEPlatformParser.cxx cmVSSetupHelper.cxx cmVSSetupHelper.h + cmVSSolution.cxx + cmVSSolution.h cmVSVersion.h ) diff --git a/Source/cmGlobalVisualStudio14Generator.cxx b/Source/cmGlobalVisualStudio14Generator.cxx index 5ba394f81b..e37ed0be74 100644 --- a/Source/cmGlobalVisualStudio14Generator.cxx +++ b/Source/cmGlobalVisualStudio14Generator.cxx @@ -521,60 +521,3 @@ std::string cmGlobalVisualStudio14Generator::GetWindows10SDKVersion( // Return an empty string return std::string(); } - -void cmGlobalVisualStudio14Generator::AddSolutionItems(cmLocalGenerator* root, - VSFolders& vsFolders) -{ - cmValue n = root->GetMakefile()->GetProperty("VS_SOLUTION_ITEMS"); - if (cmNonempty(n)) { - cmMakefile* makefile = root->GetMakefile(); - - std::vector sourceGroups = makefile->GetSourceGroups(); - - cmVisualStudioFolder* defaultFolder = nullptr; - - std::vector pathComponents = { - makefile->GetCurrentSourceDirectory(), - "", - "", - }; - - for (std::string const& relativePath : cmList(n)) { - pathComponents[2] = relativePath; - - std::string fullPath = cmSystemTools::FileIsFullPath(relativePath) - ? relativePath - : cmSystemTools::JoinPath(pathComponents); - - cmSourceGroup* sg = makefile->FindSourceGroup(fullPath, sourceGroups); - - cmVisualStudioFolder* folder = nullptr; - if (!sg->GetFullName().empty()) { - std::string folderPath = sg->GetFullName(); - // Source groups use '\' while solution folders use '/'. - cmSystemTools::ReplaceString(folderPath, "\\", "/"); - folder = vsFolders.Create(folderPath); - } else { - // Lazily initialize the default solution items folder. - if (defaultFolder == nullptr) { - defaultFolder = vsFolders.Create("Solution Items"); - } - folder = defaultFolder; - } - - folder->SolutionItems.insert(fullPath); - } - } -} - -void cmGlobalVisualStudio14Generator::WriteFolderSolutionItems( - std::ostream& fout, cmVisualStudioFolder const& folder) const -{ - fout << "\tProjectSection(SolutionItems) = preProject\n"; - - for (std::string const& item : folder.SolutionItems) { - fout << "\t\t" << item << " = " << item << "\n"; - } - - fout << "\tEndProjectSection\n"; -} diff --git a/Source/cmGlobalVisualStudio14Generator.h b/Source/cmGlobalVisualStudio14Generator.h index a71326f934..d9d928a73f 100644 --- a/Source/cmGlobalVisualStudio14Generator.h +++ b/Source/cmGlobalVisualStudio14Generator.h @@ -66,11 +66,6 @@ protected: std::string GetWindows10SDKVersion(cmMakefile* mf); - void AddSolutionItems(cmLocalGenerator* root, VSFolders& vsFolders) override; - - void WriteFolderSolutionItems( - std::ostream& fout, cmVisualStudioFolder const& folder) const override; - private: class Factory; friend class Factory; diff --git a/Source/cmGlobalVisualStudio71Generator.cxx b/Source/cmGlobalVisualStudio71Generator.cxx index 26ff741b0f..59f3233346 100644 --- a/Source/cmGlobalVisualStudio71Generator.cxx +++ b/Source/cmGlobalVisualStudio71Generator.cxx @@ -2,167 +2,9 @@ file LICENSE.rst or https://cmake.org/licensing for details. */ #include "cmGlobalVisualStudio71Generator.h" -#include -#include - -#include "cmGeneratorTarget.h" -#include "cmGlobalGenerator.h" -#include "cmGlobalVisualStudioGenerator.h" -#include "cmList.h" -#include "cmListFileCache.h" -#include "cmLocalGenerator.h" -#include "cmMakefile.h" -#include "cmStringAlgorithms.h" -#include "cmSystemTools.h" - class cmake; cmGlobalVisualStudio71Generator::cmGlobalVisualStudio71Generator(cmake* cm) : cmGlobalVisualStudio7Generator(cm) { - this->ProjectConfigurationSectionName = "ProjectConfiguration"; -} - -void cmGlobalVisualStudio71Generator::WriteSLNFile( - std::ostream& fout, cmLocalGenerator* root, - OrderedTargetDependSet const& orderedProjectTargets, - VSFolders const& vsFolders) const -{ - std::vector configs = - root->GetMakefile()->GetGeneratorConfigs(cmMakefile::ExcludeEmptyConfig); - - // Write out the header for a SLN file - this->WriteSLNHeader(fout); - - // Generate folder specification. - if (!vsFolders.Folders.empty()) { - this->WriteFolders(fout, vsFolders); - } - - // Now write the actual target specification content. - this->WriteTargetsToSolution(fout, root, orderedProjectTargets); - - // Write out the configurations information for the solution - fout << "Global\n"; - // Write out the configurations for the solution - this->WriteSolutionConfigurations(fout, configs); - fout << "\tGlobalSection(" << this->ProjectConfigurationSectionName - << ") = postSolution\n"; - // Write out the configurations for all the targets in the project - this->WriteTargetConfigurations(fout, configs, orderedProjectTargets); - fout << "\tEndGlobalSection\n"; - - if (!vsFolders.Folders.empty()) { - // Write out project folders - fout << "\tGlobalSection(NestedProjects) = preSolution\n"; - this->WriteFoldersContent(fout, vsFolders); - fout << "\tEndGlobalSection\n"; - } - - // Write out global sections - this->WriteSLNGlobalSections(fout, root); - - // Write the footer for the SLN file - this->WriteSLNFooter(fout); -} - -// Write a dsp file into the SLN file, -// Note, that dependencies from executables to -// the libraries it uses are also done here -void cmGlobalVisualStudio71Generator::WriteProject( - std::ostream& fout, std::string const& dspname, std::string const& dir, - cmGeneratorTarget const* t) const -{ - // check to see if this is a fortran build - std::string ext = ".vcproj"; - char const* project = - R"(Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = ")"; - if (this->TargetIsFortranOnly(t)) { - ext = ".vfproj"; - project = R"(Project("{6989167D-11E4-40FE-8C1A-2192A86A7E90}") = ")"; - } - if (t->IsCSharpOnly()) { - ext = ".csproj"; - project = R"(Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = ")"; - } - cmValue targetExt = t->GetProperty("GENERATOR_FILE_NAME_EXT"); - if (targetExt) { - ext = *targetExt; - } - - std::string guid = this->GetGUID(dspname); - fout << project << dspname << "\", \"" << this->ConvertToSolutionPath(dir) - << (!dir.empty() ? "\\" : "") << dspname << ext << "\", \"{" << guid - << "}\"\n"; - fout << "\tProjectSection(ProjectDependencies) = postProject\n"; - this->WriteProjectDepends(fout, dspname, dir, t); - fout << "\tEndProjectSection\n"; - - fout << "EndProject\n"; -} - -// Write a dsp file into the SLN file, Note, that dependencies from -// executables to the libraries it uses are also done here -void cmGlobalVisualStudio71Generator::WriteExternalProject( - std::ostream& fout, std::string const& name, std::string const& location, - cmValue typeGuid, - std::set>> const& depends) const -{ - fout << "Project(\"{" - << (typeGuid ? *typeGuid - : std::string( - cmGlobalVisualStudio71Generator::ExternalProjectType( - location))) - << "}\") = \"" << name << "\", \"" - << this->ConvertToSolutionPath(location) << "\", \"{" - << this->GetGUID(name) << "}\"\n"; - - // write out the dependencies here VS 7.1 includes dependencies with the - // project instead of in the global section - if (!depends.empty()) { - fout << "\tProjectSection(ProjectDependencies) = postProject\n"; - for (BT> const& it : depends) { - std::string const& dep = it.Value.first; - if (this->IsDepInSolution(dep)) { - fout << "\t\t{" << this->GetGUID(dep) << "} = {" << this->GetGUID(dep) - << "}\n"; - } - } - fout << "\tEndProjectSection\n"; - } - - fout << "EndProject\n"; -} - -// Write a dsp file into the SLN file, Note, that dependencies from -// executables to the libraries it uses are also done here -void cmGlobalVisualStudio71Generator::WriteProjectConfigurations( - std::ostream& fout, std::string const& name, cmGeneratorTarget const& target, - std::vector const& configs, - std::set const& configsPartOfDefaultBuild, - std::string const& platformMapping) const -{ - std::string const& platformName = - !platformMapping.empty() ? platformMapping : this->GetPlatformName(); - std::string guid = this->GetGUID(name); - for (std::string const& i : configs) { - cmList mapConfig; - char const* dstConfig = i.c_str(); - if (target.GetProperty("EXTERNAL_MSPROJECT")) { - if (cmValue m = target.GetProperty( - cmStrCat("MAP_IMPORTED_CONFIG_", cmSystemTools::UpperCase(i)))) { - mapConfig.assign(*m); - if (!mapConfig.empty()) { - dstConfig = mapConfig[0].c_str(); - } - } - } - fout << "\t\t{" << guid << "}." << i << ".ActiveCfg = " << dstConfig << '|' - << platformName << std::endl; - auto ci = configsPartOfDefaultBuild.find(i); - if (!(ci == configsPartOfDefaultBuild.end())) { - fout << "\t\t{" << guid << "}." << i << ".Build.0 = " << dstConfig << '|' - << platformName << std::endl; - } - } } diff --git a/Source/cmGlobalVisualStudio71Generator.h b/Source/cmGlobalVisualStudio71Generator.h index f92ad272a0..97a151786c 100644 --- a/Source/cmGlobalVisualStudio71Generator.h +++ b/Source/cmGlobalVisualStudio71Generator.h @@ -2,49 +2,12 @@ file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once -#include -#include -#include -#include -#include - #include "cmGlobalVisualStudio7Generator.h" -#include "cmValue.h" -class cmGeneratorTarget; -class cmLocalGenerator; class cmake; -template -class BT; -/** \class cmGlobalVisualStudio71Generator - * \brief Write a Unix makefiles. - * - * cmGlobalVisualStudio71Generator manages UNIX build process for a tree - */ class cmGlobalVisualStudio71Generator : public cmGlobalVisualStudio7Generator { public: cmGlobalVisualStudio71Generator(cmake* cm); - -protected: - void WriteSLNFile(std::ostream& fout, cmLocalGenerator* root, - OrderedTargetDependSet const& orderedProjectTargets, - VSFolders const& vsFolders) const override; - virtual void WriteSolutionConfigurations( - std::ostream& fout, std::vector const& configs) const = 0; - void WriteProject(std::ostream& fout, std::string const& name, - std::string const& path, - cmGeneratorTarget const* t) const override; - void WriteProjectConfigurations( - std::ostream& fout, std::string const& name, - cmGeneratorTarget const& target, std::vector const& configs, - std::set const& configsPartOfDefaultBuild, - std::string const& platformMapping = "") const override; - void WriteExternalProject( - std::ostream& fout, std::string const& name, std::string const& path, - cmValue typeGuid, - std::set>> const& depends) const override; - - std::string ProjectConfigurationSectionName; }; diff --git a/Source/cmGlobalVisualStudio7Generator.cxx b/Source/cmGlobalVisualStudio7Generator.cxx index f28129f626..577cb53f4b 100644 --- a/Source/cmGlobalVisualStudio7Generator.cxx +++ b/Source/cmGlobalVisualStudio7Generator.cxx @@ -28,7 +28,6 @@ #include "cmSystemTools.h" #include "cmTarget.h" #include "cmTargetDepend.h" -#include "cmUuid.h" #include "cmVisualStudioGeneratorOptions.h" #include "cmake.h" @@ -344,11 +343,6 @@ void cmGlobalVisualStudio7Generator::OutputSLNFile( // closure of their dependencies. TargetDependSet const projectTargets = this->GetTargetsForProject(root, generators); - OrderedTargetDependSet orderedProjectTargets( - projectTargets, this->GetStartupProjectName(root)); - - VSFolders vsFolders = this->CreateSolutionFolders(orderedProjectTargets); - this->AddSolutionItems(root, vsFolders); std::string fname = GetSLNFile(root); cmGeneratedFileStream fout(fname); @@ -356,255 +350,12 @@ void cmGlobalVisualStudio7Generator::OutputSLNFile( if (!fout) { return; } - this->WriteSLNFile(fout, root, orderedProjectTargets, vsFolders); + this->WriteSLNFile(fout, root, projectTargets); if (fout.Close()) { this->FileReplacedDuringGenerate(fname); } } -void cmGlobalVisualStudio7Generator::WriteTargetConfigurations( - std::ostream& fout, std::vector const& configs, - OrderedTargetDependSet const& projectTargets) const -{ - // loop over again and write out configurations for each target - // in the solution - for (cmGeneratorTarget const* target : projectTargets) { - if (!this->IsInSolution(target)) { - continue; - } - cmValue expath = target->GetProperty("EXTERNAL_MSPROJECT"); - if (expath) { - std::set allConfigurations(configs.begin(), configs.end()); - cmValue mapping = target->GetProperty("VS_PLATFORM_MAPPING"); - this->WriteProjectConfigurations(fout, target->GetName(), *target, - configs, allConfigurations, - mapping ? *mapping : ""); - } else { - std::set const& configsPartOfDefaultBuild = - this->IsPartOfDefaultBuild(configs, projectTargets, target); - cmValue vcprojName = target->GetProperty("GENERATOR_FILE_NAME"); - if (vcprojName) { - std::string mapping; - - // On VS 19 and above, always map .NET SDK projects to "Any CPU". - if (target->IsDotNetSdkTarget() && this->Version >= VSVersion::VS16 && - !cmGlobalVisualStudio7Generator::IsReservedTarget( - target->GetName())) { - mapping = "Any CPU"; - } - this->WriteProjectConfigurations(fout, *vcprojName, *target, configs, - configsPartOfDefaultBuild, mapping); - } - } - } -} - -cmGlobalVisualStudio7Generator::VSFolders -cmGlobalVisualStudio7Generator::CreateSolutionFolders( - OrderedTargetDependSet const& orderedProjectTargets) -{ - VSFolders vsFolders; - if (!this->UseFolderProperty()) { - return vsFolders; - } - for (cmGeneratorTarget const* target : orderedProjectTargets) { - if (this->IsInSolution(target) && - (target->GetProperty("EXTERNAL_MSPROJECT") || - target->GetProperty("GENERATOR_FILE_NAME"))) { - // Create "solution folder" information from FOLDER target property - if (cmVisualStudioFolder* folder = - vsFolders.Create(target->GetEffectiveFolderName())) { - folder->Projects.insert(target->GetName()); - } - } - } - return vsFolders; -} - -cmVisualStudioFolder* cmGlobalVisualStudio7Generator::VSFolders::Create( - std::string const& path) -{ - if (path.empty()) { - return nullptr; - } - - std::vector tokens = - cmSystemTools::SplitString(path, '/', false); - - std::string cumulativePath; - - for (std::string const& iter : tokens) { - if (iter.empty()) { - continue; - } - - if (cumulativePath.empty()) { - cumulativePath = cmStrCat("CMAKE_FOLDER_GUID_", iter); - } else { - this->Folders[cumulativePath].Projects.insert( - cmStrCat(cumulativePath, '/', iter)); - - cumulativePath = cmStrCat(cumulativePath, '/', iter); - } - } - - if (cumulativePath.empty()) { - return nullptr; - } - - return &this->Folders[cumulativePath]; -} - -void cmGlobalVisualStudio7Generator::WriteTargetsToSolution( - std::ostream& fout, cmLocalGenerator* root, - OrderedTargetDependSet const& projectTargets) const -{ - std::vector configs = - root->GetMakefile()->GetGeneratorConfigs(cmMakefile::ExcludeEmptyConfig); - - for (cmGeneratorTarget const* target : projectTargets) { - if (!this->IsInSolution(target)) { - continue; - } - // handle external vc project files - cmValue expath = target->GetProperty("EXTERNAL_MSPROJECT"); - if (expath) { - std::string project = target->GetName(); - std::string const& location = *expath; - - this->WriteExternalProject(fout, project, location, - target->GetProperty("VS_PROJECT_TYPE"), - target->GetUtilities()); - } else { - cmValue vcprojName = target->GetProperty("GENERATOR_FILE_NAME"); - if (vcprojName) { - cmLocalGenerator* lg = target->GetLocalGenerator(); - std::string dir = lg->GetCurrentBinaryDirectory(); - dir = root->MaybeRelativeToCurBinDir(dir); - if (dir == "."_s) { - dir.clear(); // msbuild cannot handle ".\" prefix - } - this->WriteProject(fout, *vcprojName, dir, target); - } - } - } -} - -void cmGlobalVisualStudio7Generator::WriteFolders( - std::ostream& fout, VSFolders const& vsFolders) const -{ - cm::string_view const prefix = "CMAKE_FOLDER_GUID_"; - std::string guidProjectTypeFolder = "2150E333-8FDC-42A3-9474-1A3956D46DE8"; - for (auto const& iter : vsFolders.Folders) { - std::string fullName = iter.first; - std::string guid = this->GetGUID(fullName); - - std::replace(fullName.begin(), fullName.end(), '/', '\\'); - if (cmHasPrefix(fullName, prefix)) { - fullName = fullName.substr(prefix.size()); - } - - std::string nameOnly = cmSystemTools::GetFilenameName(fullName); - - fout << "Project(\"{" << guidProjectTypeFolder << "}\") = \"" << nameOnly - << "\", \"" << fullName << "\", \"{" << guid << "}\"\n"; - - if (!iter.second.SolutionItems.empty()) { - this->WriteFolderSolutionItems(fout, iter.second); - } - - fout << "EndProject\n"; - } -} - -void cmGlobalVisualStudio7Generator::WriteFoldersContent( - std::ostream& fout, VSFolders const& vsFolders) const -{ - for (auto const& iter : vsFolders.Folders) { - std::string key(iter.first); - std::string guidParent(this->GetGUID(key)); - - for (std::string const& it : iter.second.Projects) { - std::string const& value(it); - std::string guid(this->GetGUID(value)); - - fout << "\t\t{" << guid << "} = {" << guidParent << "}\n"; - } - } -} - -void cmGlobalVisualStudio7Generator::WriteSLNGlobalSections( - std::ostream& fout, cmLocalGenerator* root) const -{ - std::string const guid = - this->GetGUID(cmStrCat(root->GetProjectName(), ".sln")); - bool extensibilityGlobalsOverridden = false; - bool extensibilityAddInsOverridden = false; - std::vector const propKeys = - root->GetMakefile()->GetPropertyKeys(); - for (std::string const& it : propKeys) { - if (cmHasLiteralPrefix(it, "VS_GLOBAL_SECTION_")) { - std::string sectionType; - std::string name = it.substr(18); - if (cmHasLiteralPrefix(name, "PRE_")) { - name = name.substr(4); - sectionType = "preSolution"; - } else if (cmHasLiteralPrefix(name, "POST_")) { - name = name.substr(5); - sectionType = "postSolution"; - } else { - continue; - } - if (!name.empty()) { - bool addGuid = false; - if (name == "ExtensibilityGlobals"_s && - sectionType == "postSolution"_s) { - addGuid = true; - extensibilityGlobalsOverridden = true; - } else if (name == "ExtensibilityAddIns"_s && - sectionType == "postSolution"_s) { - extensibilityAddInsOverridden = true; - } - fout << "\tGlobalSection(" << name << ") = " << sectionType << '\n'; - cmValue p = root->GetMakefile()->GetProperty(it); - cmList keyValuePairs{ *p }; - for (std::string const& itPair : keyValuePairs) { - std::string::size_type const posEqual = itPair.find('='); - if (posEqual != std::string::npos) { - std::string const key = - cmTrimWhitespace(itPair.substr(0, posEqual)); - std::string const value = - cmTrimWhitespace(itPair.substr(posEqual + 1)); - fout << "\t\t" << key << " = " << value << '\n'; - if (key == "SolutionGuid"_s) { - addGuid = false; - } - } - } - if (addGuid) { - fout << "\t\tSolutionGuid = {" << guid << "}\n"; - } - fout << "\tEndGlobalSection\n"; - } - } - } - if (!extensibilityGlobalsOverridden) { - fout << "\tGlobalSection(ExtensibilityGlobals) = postSolution\n" - << "\t\tSolutionGuid = {" << guid << "}\n" - << "\tEndGlobalSection\n"; - } - if (!extensibilityAddInsOverridden) { - fout << "\tGlobalSection(ExtensibilityAddIns) = postSolution\n" - << "\tEndGlobalSection\n"; - } -} - -// Standard end of dsw file -void cmGlobalVisualStudio7Generator::WriteSLNFooter(std::ostream& fout) const -{ - fout << "EndGlobal\n"; -} - void cmGlobalVisualStudio7Generator::AppendDirectoryForConfig( std::string const& prefix, std::string const& config, std::string const& suffix, std::string& dir) diff --git a/Source/cmGlobalVisualStudio7Generator.h b/Source/cmGlobalVisualStudio7Generator.h index ac9e5a75dd..f5721b348e 100644 --- a/Source/cmGlobalVisualStudio7Generator.h +++ b/Source/cmGlobalVisualStudio7Generator.h @@ -25,12 +25,6 @@ class cmake; template class BT; -struct cmVisualStudioFolder -{ - std::set Projects; - std::set SolutionItems; -}; - /** \class cmGlobalVisualStudio7Generator * \brief Write a Unix makefiles. * @@ -126,12 +120,6 @@ protected: void Generate() override; - struct VSFolders - { - std::map Folders; - cmVisualStudioFolder* Create(std::string const& path); - }; - std::string const& GetDevEnvCommand(); virtual std::string FindDevEnvCommand(); @@ -139,49 +127,6 @@ protected: virtual void OutputSLNFile(cmLocalGenerator* root, std::vector& generators); - virtual void WriteSLNFile( - std::ostream& fout, cmLocalGenerator* root, - OrderedTargetDependSet const& orderedProjectTargets, - VSFolders const& vsFolders) const = 0; - virtual void WriteProject(std::ostream& fout, std::string const& name, - std::string const& path, - cmGeneratorTarget const* t) const = 0; - virtual void WriteProjectDepends(std::ostream& fout, std::string const& name, - std::string const& path, - cmGeneratorTarget const* t) const = 0; - virtual void WriteProjectConfigurations( - std::ostream& fout, std::string const& name, - cmGeneratorTarget const& target, std::vector const& configs, - std::set const& configsPartOfDefaultBuild, - std::string const& platformMapping = "") const = 0; - virtual void WriteSLNGlobalSections(std::ostream& fout, - cmLocalGenerator* root) const; - virtual void WriteSLNFooter(std::ostream& fout) const; - - VSFolders CreateSolutionFolders( - OrderedTargetDependSet const& orderedProjectTargets); - - virtual void WriteTargetsToSolution( - std::ostream& fout, cmLocalGenerator* root, - OrderedTargetDependSet const& projectTargets) const; - virtual void WriteTargetConfigurations( - std::ostream& fout, std::vector const& configs, - OrderedTargetDependSet const& projectTargets) const; - - virtual void WriteExternalProject( - std::ostream& fout, std::string const& name, std::string const& path, - cmValue typeGuid, - std::set>> const& dependencies) const = 0; - - virtual void WriteFolders(std::ostream& fout, - VSFolders const& vsFolders) const; - virtual void WriteFoldersContent(std::ostream& fout, - VSFolders const& vsFolders) const; - - virtual void AddSolutionItems(cmLocalGenerator* root, - VSFolders& vsFolders) = 0; - virtual void WriteFolderSolutionItems( - std::ostream& fout, cmVisualStudioFolder const& folder) const = 0; bool MarmasmEnabled; bool MasmEnabled; diff --git a/Source/cmGlobalVisualStudio8Generator.cxx b/Source/cmGlobalVisualStudio8Generator.cxx index 9e2b303847..022ca95174 100644 --- a/Source/cmGlobalVisualStudio8Generator.cxx +++ b/Source/cmGlobalVisualStudio8Generator.cxx @@ -42,7 +42,6 @@ cmGlobalVisualStudio8Generator::cmGlobalVisualStudio8Generator( cmake* cm, std::string const& name) : cmGlobalVisualStudio71Generator(cm) { - this->ProjectConfigurationSectionName = "ProjectConfigurationPlatforms"; this->Name = name; this->ExtraFlagTable = cmGlobalVisualStudio8Generator::GetExtraFlagTableVS8(); @@ -356,59 +355,6 @@ void cmGlobalVisualStudio8Generator::AddExtraIDETargets() } } -void cmGlobalVisualStudio8Generator::WriteSolutionConfigurations( - std::ostream& fout, std::vector const& configs) const -{ - fout << "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n"; - for (std::string const& i : configs) { - fout << "\t\t" << i << '|' << this->GetPlatformName() << " = " << i << '|' - << this->GetPlatformName() << '\n'; - } - fout << "\tEndGlobalSection\n"; -} - -void cmGlobalVisualStudio8Generator::WriteProjectConfigurations( - std::ostream& fout, std::string const& name, cmGeneratorTarget const& target, - std::vector const& configs, - std::set const& configsPartOfDefaultBuild, - std::string const& platformMapping) const -{ - std::string guid = this->GetGUID(name); - for (std::string const& i : configs) { - cmList mapConfig; - char const* dstConfig = i.c_str(); - if (target.GetProperty("EXTERNAL_MSPROJECT")) { - if (cmValue m = target.GetProperty( - cmStrCat("MAP_IMPORTED_CONFIG_", cmSystemTools::UpperCase(i)))) { - mapConfig.assign(*m); - if (!mapConfig.empty()) { - dstConfig = mapConfig[0].c_str(); - } - } - } - fout << "\t\t{" << guid << "}." << i << '|' << this->GetPlatformName() - << ".ActiveCfg = " << dstConfig << '|' - << (!platformMapping.empty() ? platformMapping - : this->GetPlatformName()) - << '\n'; - auto ci = configsPartOfDefaultBuild.find(i); - if (!(ci == configsPartOfDefaultBuild.end())) { - fout << "\t\t{" << guid << "}." << i << '|' << this->GetPlatformName() - << ".Build.0 = " << dstConfig << '|' - << (!platformMapping.empty() ? platformMapping - : this->GetPlatformName()) - << '\n'; - } - if (this->NeedsDeploy(target, dstConfig)) { - fout << "\t\t{" << guid << "}." << i << '|' << this->GetPlatformName() - << ".Deploy.0 = " << dstConfig << '|' - << (!platformMapping.empty() ? platformMapping - : this->GetPlatformName()) - << '\n'; - } - } -} - bool cmGlobalVisualStudio8Generator::NeedsDeploy( cmGeneratorTarget const& target, char const* config) const { @@ -444,21 +390,6 @@ bool cmGlobalVisualStudio8Generator::TargetSystemSupportsDeployment() const return this->TargetsWindowsCE(); } -void cmGlobalVisualStudio8Generator::WriteProjectDepends( - std::ostream& fout, std::string const&, std::string const&, - cmGeneratorTarget const* gt) const -{ - TargetDependSet const& unordered = this->GetTargetDirectDepends(gt); - OrderedTargetDependSet depends(unordered, std::string()); - for (cmTargetDepend const& i : depends) { - if (!this->IsInSolution(i)) { - continue; - } - std::string guid = this->GetGUID(i->GetName()); - fout << "\t\t{" << guid << "} = {" << guid << "}\n"; - } -} - bool cmGlobalVisualStudio8Generator::NeedLinkLibraryDependencies( cmGeneratorTarget* target) { diff --git a/Source/cmGlobalVisualStudio8Generator.h b/Source/cmGlobalVisualStudio8Generator.h index 6fb5a132f6..68278c4169 100644 --- a/Source/cmGlobalVisualStudio8Generator.h +++ b/Source/cmGlobalVisualStudio8Generator.h @@ -70,17 +70,6 @@ protected: bool TargetSystemSupportsDeployment() const override; static cmIDEFlagTable const* GetExtraFlagTableVS8(); - void WriteSolutionConfigurations( - std::ostream& fout, - std::vector const& configs) const override; - void WriteProjectConfigurations( - std::ostream& fout, std::string const& name, - cmGeneratorTarget const& target, std::vector const& configs, - std::set const& configsPartOfDefaultBuild, - std::string const& platformMapping = "") const override; - void WriteProjectDepends(std::ostream& fout, std::string const& name, - std::string const& path, - cmGeneratorTarget const* t) const override; std::string Name; std::string WindowsCEVersion; diff --git a/Source/cmGlobalVisualStudioGenerator.cxx b/Source/cmGlobalVisualStudioGenerator.cxx index e0454362f7..eff455afef 100644 --- a/Source/cmGlobalVisualStudioGenerator.cxx +++ b/Source/cmGlobalVisualStudioGenerator.cxx @@ -123,45 +123,6 @@ char const* cmGlobalVisualStudioGenerator::GetIDEVersion() const return ""; } -void cmGlobalVisualStudioGenerator::WriteSLNHeader(std::ostream& fout) const -{ - char utf8bom[] = { char(0xEF), char(0xBB), char(0xBF) }; - fout.write(utf8bom, 3); - fout << '\n'; - - switch (this->Version) { - case cmGlobalVisualStudioGenerator::VSVersion::VS14: - // Visual Studio 14 writes .sln format 12.00 - fout << "Microsoft Visual Studio Solution File, Format Version 12.00\n"; - if (this->ExpressEdition) { - fout << "# Visual Studio Express 14 for Windows Desktop\n"; - } else { - fout << "# Visual Studio 14\n"; - } - break; - case cmGlobalVisualStudioGenerator::VSVersion::VS15: - // Visual Studio 15 writes .sln format 12.00 - fout << "Microsoft Visual Studio Solution File, Format Version 12.00\n"; - fout << "# Visual Studio 15\n"; - break; - case cmGlobalVisualStudioGenerator::VSVersion::VS16: - // Visual Studio 16 writes .sln format 12.00 - fout << "Microsoft Visual Studio Solution File, Format Version 12.00\n"; - fout << "# Visual Studio Version 16\n"; - break; - case cmGlobalVisualStudioGenerator::VSVersion::VS17: - // Visual Studio 17 writes .sln format 12.00 - fout << "Microsoft Visual Studio Solution File, Format Version 12.00\n"; - fout << "# Visual Studio Version 17\n"; - break; - case cmGlobalVisualStudioGenerator::VSVersion::VS18: - // Visual Studio 18 writes .sln format 12.00 - fout << "Microsoft Visual Studio Solution File, Format Version 12.00\n"; - fout << "# Visual Studio Version 18\n"; - break; - } -} - std::string cmGlobalVisualStudioGenerator::GetRegistryBase() { return cmGlobalVisualStudioGenerator::GetRegistryBase(this->GetIDEVersion()); @@ -813,23 +774,8 @@ bool cmGlobalVisualStudioGenerator::Open(std::string const& bindir, return std::async(std::launch::async, OpenSolution, sln).get(); } -std::string cmGlobalVisualStudioGenerator::ConvertToSolutionPath( - std::string const& path) const -{ - // Convert to backslashes. Do not use ConvertToOutputPath because - // we will add quoting ourselves, and we know these projects always - // use windows slashes. - std::string d = path; - std::string::size_type pos = 0; - while ((pos = d.find('/', pos)) != std::string::npos) { - d[pos++] = '\\'; - } - return d; -} - bool cmGlobalVisualStudioGenerator::IsDependedOn( - OrderedTargetDependSet const& projectTargets, - cmGeneratorTarget const* gtIn) const + TargetDependSet const& projectTargets, cmGeneratorTarget const* gtIn) const { return std::any_of(projectTargets.begin(), projectTargets.end(), [this, gtIn](cmTargetDepend const& l) { @@ -841,8 +787,7 @@ bool cmGlobalVisualStudioGenerator::IsDependedOn( std::set cmGlobalVisualStudioGenerator::IsPartOfDefaultBuild( std::vector const& configs, - OrderedTargetDependSet const& projectTargets, - cmGeneratorTarget const* target) const + TargetDependSet const& projectTargets, cmGeneratorTarget const* target) const { std::set activeConfigs; // if it is a utility target then only make it part of the @@ -906,3 +851,243 @@ std::string cmGlobalVisualStudioGenerator::GetGUID( return cmSystemTools::UpperCase(guid); } + +cm::VS::Solution::Folder* cmGlobalVisualStudioGenerator::CreateSolutionFolder( + cm::VS::Solution& solution, cm::string_view rawName) const +{ + cm::VS::Solution::Folder* folder = nullptr; + std::string canonicalName; + for (std::string::size_type cur = 0;;) { + static std::string delims = "/\\"; + cur = rawName.find_first_not_of(delims, cur); + if (cur == std::string::npos) { + break; + } + std::string::size_type end = rawName.find_first_of(delims, cur); + cm::string_view f = end == std::string::npos + ? rawName.substr(cur) + : rawName.substr(cur, end - cur); + canonicalName = + canonicalName.empty() ? std::string(f) : cmStrCat(canonicalName, '/', f); + cm::VS::Solution::Folder* nextFolder = solution.GetFolder(canonicalName); + if (nextFolder->Id.empty()) { + nextFolder->Id = + this->GetGUID(cmStrCat("CMAKE_FOLDER_GUID_"_s, canonicalName)); + if (folder) { + folder->Folders.emplace_back(nextFolder); + } + solution.Folders.emplace_back(nextFolder); + } + folder = nextFolder; + cur = end; + } + return folder; +} + +cm::VS::Solution cmGlobalVisualStudioGenerator::CreateSolution( + cmLocalGenerator const* root, TargetDependSet const& projectTargets) const +{ + using namespace cm::VS; + Solution solution; + solution.VSVersion = this->Version; + solution.VSExpress = + this->ExpressEdition ? VersionExpress::Yes : VersionExpress::No; + solution.Platform = this->GetPlatformName(); + solution.Configs = + root->GetMakefile()->GetGeneratorConfigs(cmMakefile::ExcludeEmptyConfig); + solution.StartupProject = this->GetStartupProjectName(root); + + auto addProject = [this, useFolders = this->UseFolderProperty(), + &solution](cmGeneratorTarget const* gt, + Solution::Project const* p) { + if (Solution::Folder* const folder = useFolders + ? this->CreateSolutionFolder(solution, gt->GetEffectiveFolderName()) + : nullptr) { + folder->Projects.emplace_back(p); + } else { + solution.Projects.emplace_back(p); + } + }; + + for (cmTargetDepend const& projectTarget : projectTargets) { + cmGeneratorTarget const* gt = projectTarget; + if (!this->IsInSolution(gt)) { + continue; + } + + Solution::Project* project = solution.GetProject(gt->GetName()); + project->Id = this->GetGUID(gt->GetName()); + + std::set const& includeConfigs = + this->IsPartOfDefaultBuild(solution.Configs, projectTargets, gt); + auto addProjectConfig = + [this, project, gt, &includeConfigs](std::string const& solutionConfig, + std::string const& projectConfig) { + bool const build = + includeConfigs.find(solutionConfig) != includeConfigs.end(); + bool const deploy = this->NeedsDeploy(*gt, solutionConfig.c_str()); + project->Configs.emplace_back( + Solution::ProjectConfig{ projectConfig, build, deploy }); + }; + + if (cmValue expath = gt->GetProperty("EXTERNAL_MSPROJECT")) { + project->Path = *expath; + cmValue const projectType = gt->GetProperty("VS_PROJECT_TYPE"); + if (!projectType.IsEmpty()) { + project->TypeId = *projectType; + } else { + project->TypeId = Solution::Project::TypeIdDefault; + } + for (std::string const& config : solution.Configs) { + cmList mapConfig{ gt->GetProperty(cmStrCat( + "MAP_IMPORTED_CONFIG_", cmSystemTools::UpperCase(config))) }; + addProjectConfig(config, !mapConfig.empty() ? mapConfig[0] : config); + } + cmValue platformMapping = gt->GetProperty("VS_PLATFORM_MAPPING"); + project->Platform = + !platformMapping.IsEmpty() ? *platformMapping : solution.Platform; + for (BT> const& i : gt->GetUtilities()) { + std::string const& dep = i.Value.first; + if (this->IsDepInSolution(dep)) { + project->BuildDependencies.emplace_back(solution.GetProject(dep)); + } + } + addProject(gt, project); + continue; + } + + cmValue vcprojName = gt->GetProperty("GENERATOR_FILE_NAME"); + cmValue vcprojType = gt->GetProperty("GENERATOR_FILE_NAME_EXT"); + if (vcprojName && vcprojType) { + cmLocalGenerator* lg = gt->GetLocalGenerator(); + std::string dir = + root->MaybeRelativeToCurBinDir(lg->GetCurrentBinaryDirectory()); + if (dir == "."_s) { + dir.clear(); + } else if (!cmHasLiteralSuffix(dir, "/")) { + dir += "/"; + } + + project->Path = cmStrCat(dir, *vcprojName, *vcprojType); + if (this->TargetIsFortranOnly(gt)) { + project->TypeId = Solution::Project::TypeIdFortran; + } else if (gt->IsCSharpOnly()) { + project->TypeId = Solution::Project::TypeIdCSharp; + } else { + project->TypeId = Solution::Project::TypeIdDefault; + } + + project->Platform = + // On VS 19 and above, always map .NET SDK projects to "Any CPU". + (gt->IsDotNetSdkTarget() && this->Version >= VSVersion::VS16 && + !cmGlobalVisualStudioGenerator::IsReservedTarget(gt->GetName())) + ? "Any CPU" + : solution.Platform; + + // Add solution-level dependencies. + TargetDependSet const& depends = this->GetTargetDirectDepends(gt); + for (cmTargetDepend const& dep : depends) { + if (this->IsInSolution(dep)) { + project->BuildDependencies.emplace_back( + solution.GetProject(dep->GetName())); + } + } + + for (std::string const& config : solution.Configs) { + addProjectConfig(config, config); + } + + addProject(gt, project); + continue; + } + } + + cmMakefile* mf = root->GetMakefile(); + // Unfortunately we have to copy the source groups because + // FindSourceGroup uses a regex which is modifying the group. + std::vector sourceGroups = mf->GetSourceGroups(); + std::vector items = + cmList{ root->GetMakefile()->GetProperty("VS_SOLUTION_ITEMS") }; + for (std::string item : items) { + if (!cmSystemTools::FileIsFullPath(item)) { + item = + cmSystemTools::CollapseFullPath(item, mf->GetCurrentSourceDirectory()); + } + cmSourceGroup* sg = mf->FindSourceGroup(item, sourceGroups); + std::string folderName = sg->GetFullName(); + if (folderName.empty()) { + folderName = "Solution Items"_s; + } + Solution::Folder* folder = + this->CreateSolutionFolder(solution, folderName); + folder->Files.emplace(std::move(item)); + } + + Solution::PropertyGroup* pgExtensibilityGlobals = nullptr; + Solution::PropertyGroup* pgExtensibilityAddIns = nullptr; + std::vector const propKeys = + root->GetMakefile()->GetPropertyKeys(); + for (std::string const& it : propKeys) { + if (!cmHasLiteralPrefix(it, "VS_GLOBAL_SECTION_")) { + continue; + } + std::string name = it.substr(18); + Solution::PropertyGroup::Load scope; + if (cmHasLiteralPrefix(name, "PRE_")) { + name = name.substr(4); + scope = Solution::PropertyGroup::Load::Pre; + } else if (cmHasLiteralPrefix(name, "POST_")) { + name = name.substr(5); + scope = Solution::PropertyGroup::Load::Post; + } else { + continue; + } + if (name.empty()) { + continue; + } + Solution::PropertyGroup* pg = solution.GetPropertyGroup(name); + solution.PropertyGroups.emplace_back(pg); + pg->Scope = scope; + cmList keyValuePairs{ root->GetMakefile()->GetProperty(it) }; + for (std::string const& itPair : keyValuePairs) { + std::string::size_type const posEqual = itPair.find('='); + if (posEqual != std::string::npos) { + std::string key = cmTrimWhitespace(itPair.substr(0, posEqual)); + std::string value = cmTrimWhitespace(itPair.substr(posEqual + 1)); + pg->Map.emplace(std::move(key), std::move(value)); + } + } + if (name == "ExtensibilityGlobals"_s) { + pgExtensibilityGlobals = pg; + } else if (name == "ExtensibilityAddIns"_s) { + pgExtensibilityAddIns = pg; + } + } + + if (!pgExtensibilityGlobals) { + pgExtensibilityGlobals = + solution.GetPropertyGroup("ExtensibilityGlobals"_s); + solution.PropertyGroups.emplace_back(pgExtensibilityGlobals); + } + std::string const solutionGuid = + this->GetGUID(cmStrCat(root->GetProjectName(), ".sln")); + pgExtensibilityGlobals->Map.emplace("SolutionGuid", + cmStrCat('{', solutionGuid, '}')); + + if (!pgExtensibilityAddIns) { + pgExtensibilityAddIns = solution.GetPropertyGroup("ExtensibilityAddIns"_s); + solution.PropertyGroups.emplace_back(pgExtensibilityAddIns); + } + + solution.CanonicalizeOrder(); + + return solution; +} + +void cmGlobalVisualStudioGenerator::WriteSLNFile( + std::ostream& fout, cmLocalGenerator* root, + TargetDependSet const& projectTargets) const +{ + cm::VS::Solution const solution = this->CreateSolution(root, projectTargets); + WriteSln(fout, solution); +} diff --git a/Source/cmGlobalVisualStudioGenerator.h b/Source/cmGlobalVisualStudioGenerator.h index 46d98f7e99..6e42981cd6 100644 --- a/Source/cmGlobalVisualStudioGenerator.h +++ b/Source/cmGlobalVisualStudioGenerator.h @@ -14,6 +14,7 @@ #include "cmGlobalGenerator.h" #include "cmTargetDepend.h" +#include "cmVSSolution.h" #include "cmVSVersion.h" #include "cmValue.h" @@ -169,16 +170,12 @@ protected: char const* GetIDEVersion() const; - void WriteSLNHeader(std::ostream& fout) const; - VSVersion Version; bool ExpressEdition; std::string GeneratorPlatform; std::string DefaultPlatformName; - std::string ConvertToSolutionPath(std::string const& path) const; - /** Return true if the configuration needs to be deployed */ virtual bool NeedsDeploy(cmGeneratorTarget const& target, char const* config) const = 0; @@ -188,12 +185,20 @@ protected: std::set IsPartOfDefaultBuild( std::vector const& configs, - OrderedTargetDependSet const& projectTargets, + TargetDependSet const& projectTargets, cmGeneratorTarget const* target) const; - bool IsDependedOn(OrderedTargetDependSet const& projectTargets, + bool IsDependedOn(TargetDependSet const& projectTargets, cmGeneratorTarget const* target) const; std::map GUIDMap; + cm::VS::Solution CreateSolution(cmLocalGenerator const* root, + TargetDependSet const& projectTargets) const; + cm::VS::Solution::Folder* CreateSolutionFolder( + cm::VS::Solution& solution, cm::string_view rawName) const; + + void WriteSLNFile(std::ostream& fout, cmLocalGenerator* root, + TargetDependSet const& projectTargets) const; + private: virtual std::string GetVSMakeProgram() = 0; void PrintCompilerAdvice(std::ostream&, std::string const&, diff --git a/Source/cmGlobalVisualStudioVersionedGenerator.cxx b/Source/cmGlobalVisualStudioVersionedGenerator.cxx index 238475782a..fe1b79097b 100644 --- a/Source/cmGlobalVisualStudioVersionedGenerator.cxx +++ b/Source/cmGlobalVisualStudioVersionedGenerator.cxx @@ -8,6 +8,7 @@ #include #include +#include #include #include "cmsys/FStream.hxx" diff --git a/Source/cmGlobalVisualStudioVersionedGenerator.h b/Source/cmGlobalVisualStudioVersionedGenerator.h index bb1def3096..70102cfe06 100644 --- a/Source/cmGlobalVisualStudioVersionedGenerator.h +++ b/Source/cmGlobalVisualStudioVersionedGenerator.h @@ -8,6 +8,7 @@ #include #include +#include #include "cmGlobalVisualStudio10Generator.h" #include "cmGlobalVisualStudio14Generator.h" diff --git a/Source/cmVSSolution.cxx b/Source/cmVSSolution.cxx new file mode 100644 index 0000000000..99323746bf --- /dev/null +++ b/Source/cmVSSolution.cxx @@ -0,0 +1,283 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ +#include "cmVSSolution.h" + +#include +#include +#include +#include + +#include +#include + +#include "cmSystemTools.h" + +namespace cm { +namespace VS { + +cm::string_view const Solution::Project::TypeIdDefault = + "8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942"_s; +cm::string_view const Solution::Project::TypeIdCSharp = + "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC"_s; +cm::string_view const Solution::Project::TypeIdFortran = + "6989167D-11E4-40FE-8C1A-2192A86A7E90"_s; +cm::string_view const Solution::Folder::TypeId = + "2150E333-8FDC-42A3-9474-1A3956D46DE8"_s; + +std::vector Solution::GetAllProjects() const +{ + std::vector projects; + projects.reserve(this->ProjectMap.size()); + for (Project const* project : this->Projects) { + projects.emplace_back(project); + } + for (Folder const* folder : this->Folders) { + for (Project const* project : folder->Projects) { + projects.emplace_back(project); + } + } + return projects; +} + +namespace { +template +T* GetEntry(std::map>& entryMap, + cm::string_view name) +{ + auto i = entryMap.find(name); + if (i == entryMap.end()) { + auto p = cm::make_unique(); + p->Name = name; + i = entryMap.emplace(p->Name, std::move(p)).first; + } + return i->second.get(); +} +} + +Solution::Folder* Solution::GetFolder(cm::string_view name) +{ + return GetEntry(this->FolderMap, name); +} + +Solution::Project* Solution::GetProject(cm::string_view name) +{ + return GetEntry(this->ProjectMap, name); +} + +Solution::PropertyGroup* Solution::GetPropertyGroup(cm::string_view name) +{ + return GetEntry(this->PropertyGroupMap, name); +} + +namespace { +struct OrderByName +{ + template + bool operator()(T const* l, T const* r) const + { + return l->Name < r->Name; + } +}; +} + +void Solution::CanonicalizeOrder() +{ + std::sort(this->Folders.begin(), this->Folders.end(), OrderByName()); + for (auto& fi : this->FolderMap) { + Folder* folder = fi.second.get(); + std::sort(folder->Folders.begin(), folder->Folders.end(), OrderByName()); + std::sort(folder->Projects.begin(), folder->Projects.end(), OrderByName()); + } + std::sort(this->Projects.begin(), this->Projects.end(), OrderByName()); + for (auto& pi : this->ProjectMap) { + Project* project = pi.second.get(); + std::sort(project->BuildDependencies.begin(), + project->BuildDependencies.end(), OrderByName()); + } +} + +namespace { + +void WriteSlnHeader(std::ostream& sln, Version version, VersionExpress express) +{ + char utf8bom[] = { char(0xEF), char(0xBB), char(0xBF) }; + sln.write(utf8bom, 3); + sln << '\n'; + + switch (version) { + case Version::VS14: + // Visual Studio 14 writes .sln format 12.00 + sln << "Microsoft Visual Studio Solution File, Format Version 12.00\n"; + if (express == VersionExpress::Yes) { + sln << "# Visual Studio Express 14 for Windows Desktop\n"; + } else { + sln << "# Visual Studio 14\n"; + } + break; + case Version::VS15: + // Visual Studio 15 writes .sln format 12.00 + sln << "Microsoft Visual Studio Solution File, Format Version 12.00\n"; + sln << "# Visual Studio 15\n"; + break; + case Version::VS16: + // Visual Studio 16 writes .sln format 12.00 + sln << "Microsoft Visual Studio Solution File, Format Version 12.00\n"; + sln << "# Visual Studio Version 16\n"; + break; + case Version::VS17: + // Visual Studio 17 writes .sln format 12.00 + sln << "Microsoft Visual Studio Solution File, Format Version 12.00\n"; + sln << "# Visual Studio Version 17\n"; + break; + case Version::VS18: + // Visual Studio 18 writes .sln format 12.00 + sln << "Microsoft Visual Studio Solution File, Format Version 12.00\n"; + sln << "# Visual Studio Version 18\n"; + break; + } +} + +void WriteSlnProject(std::ostream& sln, Solution::Project const& project) +{ + std::string projectPath = project.Path; + std::replace(projectPath.begin(), projectPath.end(), '/', '\\'); + sln << "Project(\"{" << project.TypeId << "}\") = \"" << project.Name + << "\", \"" << projectPath << "\", \"{" << project.Id << "}\"\n"; + sln << "\tProjectSection(ProjectDependencies) = postProject\n"; + for (Solution::Project const* d : project.BuildDependencies) { + sln << "\t\t{" << d->Id << "} = {" << d->Id << "}\n"; + } + sln << "\tEndProjectSection\n"; + sln << "EndProject\n"; +} + +void WriteSlnFolder(std::ostream& sln, Solution::Folder const& folder) +{ + std::string folderName = folder.Name; + std::replace(folderName.begin(), folderName.end(), '/', '\\'); + std::string const fileName = cmSystemTools::GetFilenameName(folder.Name); + sln << "Project(\"{" << Solution::Folder::TypeId << "}\") = \"" << fileName + << "\", \"" << folderName << "\", \"{" << folder.Id << "}\"\n"; + if (!folder.Files.empty()) { + sln << "\tProjectSection(SolutionItems) = preProject\n"; + for (std::string const& item : folder.Files) { + sln << "\t\t" << item << " = " << item << "\n"; + } + sln << "\tEndProjectSection\n"; + } + sln << "EndProject\n"; +} + +void WriteSlnSolutionConfigurationPlatforms(std::ostream& sln, + Solution const& solution) +{ + sln << "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n"; + for (std::string const& config : solution.Configs) { + sln << "\t\t" << config << '|' << solution.Platform << " = " << config + << '|' << solution.Platform << '\n'; + } + sln << "\tEndGlobalSection\n"; +} + +void WriteSlnProjectConfigurationPlatforms(std::ostream& sln, + Solution const& solution, + Solution::Project const& project) +{ + auto const writeStep = [&sln, &solution, &project](std::size_t i, + cm::string_view step) { + sln << "\t\t{" << project.Id << "}." << solution.Configs[i] << '|' + << solution.Platform << "." << step << " = " + << project.Configs[i].Config << '|' << project.Platform << '\n'; + }; + assert(project.Configs.size() == solution.Configs.size()); + for (std::size_t i = 0; i < solution.Configs.size(); ++i) { + writeStep(i, "ActiveCfg"_s); + if (project.Configs[i].Build) { + writeStep(i, "Build.0"_s); + } + if (project.Configs[i].Deploy) { + writeStep(i, "Deploy.0"_s); + } + } +} + +void WriteSlnProjectConfigurationPlatforms( + std::ostream& sln, Solution const& solution, + std::vector const& projects) +{ + sln << "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n"; + for (Solution::Project const* project : projects) { + WriteSlnProjectConfigurationPlatforms(sln, solution, *project); + } + sln << "\tEndGlobalSection\n"; +} + +void WriteSlnNestedProjects( + std::ostream& sln, std::vector const& folders) +{ + sln << "\tGlobalSection(NestedProjects) = preSolution\n"; + for (Solution::Folder const* folder : folders) { + for (Solution::Folder const* nestedFolder : folder->Folders) { + sln << "\t\t{" << nestedFolder->Id << "} = {" << folder->Id << "}\n"; + } + for (Solution::Project const* project : folder->Projects) { + sln << "\t\t{" << project->Id << "} = {" << folder->Id << "}\n"; + } + } + sln << "\tEndGlobalSection\n"; +} + +void WriteSlnPropertyGroup(std::ostream& sln, + Solution::PropertyGroup const& pg) +{ + cm::string_view const order = pg.Scope == Solution::PropertyGroup::Load::Pre + ? "preSolution"_s + : "postSolution"_s; + sln << "\tGlobalSection(" << pg.Name << ") = " << order << '\n'; + for (auto const& i : pg.Map) { + sln << "\t\t" << i.first << " = " << i.second << '\n'; + } + sln << "\tEndGlobalSection\n"; +} + +} + +void WriteSln(std::ostream& sln, Solution const& solution) +{ + assert(solution.VSVersion); + assert(solution.VSExpress); + + std::vector projects = solution.GetAllProjects(); + std::sort(projects.begin(), projects.end(), + [&solution](Solution::Project const* l, + Solution::Project const* r) -> bool { + if (r->Name == solution.StartupProject) { + return false; + } + if (l->Name == solution.StartupProject) { + return true; + } + return l->Name < r->Name; + }); + + WriteSlnHeader(sln, *solution.VSVersion, *solution.VSExpress); + for (Solution::Folder const* folder : solution.Folders) { + WriteSlnFolder(sln, *folder); + } + for (Solution::Project const* project : projects) { + WriteSlnProject(sln, *project); + } + sln << "Global\n"; + WriteSlnSolutionConfigurationPlatforms(sln, solution); + WriteSlnProjectConfigurationPlatforms(sln, solution, projects); + if (!solution.Folders.empty()) { + WriteSlnNestedProjects(sln, solution.Folders); + } + for (Solution::PropertyGroup const* pg : solution.PropertyGroups) { + WriteSlnPropertyGroup(sln, *pg); + } + sln << "EndGlobal\n"; +} + +} +} diff --git a/Source/cmVSSolution.h b/Source/cmVSSolution.h new file mode 100644 index 0000000000..adc5127200 --- /dev/null +++ b/Source/cmVSSolution.h @@ -0,0 +1,169 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file LICENSE.rst or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include +#include +#include +#include + +#include +#include +#include + +#include "cmVSVersion.h" + +namespace cm { +namespace VS { + +/** Represent a Visual Studio Solution. + In VS terminology, a "project" corresponds to a CMake "target". */ +struct Solution final +{ + Solution() = default; + Solution(Solution&&) = default; + Solution& operator=(Solution&&) = default; + + /** Represent how a project behaves under one solution config. */ + struct ProjectConfig final + { + /** Project-specific config corresponding to this solution config. + This is usually the same as the solution config, but it can map + to another config in some cases. */ + std::string Config; + + /** Does the project build under this solution config? */ + bool Build = false; + + /** Does the project deploy under this solution config? */ + bool Deploy = false; + }; + + /** Represent one project in a Solution. + This corresponds to one CMake "target". */ + struct Project final + { + /** Project name. This corresponds to the CMake "target" name. */ + std::string Name; + + /** Project GUID. */ + std::string Id; + + /** Project type GUID. */ + std::string TypeId; + + /** Path to the project file on disk. + This is either absolute or relative to the Solution file. */ + std::string Path; + + /** Project-specific platform. This is usually the same as the + Solution::Platform, but it can be different in some cases. */ + std::string Platform; + + /** Project-specific configuration corresponding to each solution config. + This vector has the same length as the Solution::Configs vector. */ + std::vector Configs; + + /** Solution-level dependencies of the project on other projects. */ + std::vector BuildDependencies; + + // Project type GUIDs used during creation. + static cm::string_view const TypeIdDefault; + static cm::string_view const TypeIdCSharp; + static cm::string_view const TypeIdFortran; + }; + + /** Represent one folder in a Solution. */ + struct Folder final + { + /** Canonical folder name. This includes parent folders separated by + forward slashes. */ + std::string Name; + + /** Folder GUID. */ + std::string Id; + + /** List of folders contained inside this folder. */ + std::vector Folders; + + /** List of projects contained inside this folder. */ + std::vector Projects; + + /** Solution-level files contained inside this folder. */ + std::set Files; + + // Folder type GUID. + static cm::string_view const TypeId; + }; + + /** Represent a group of solution-level Properties. */ + struct PropertyGroup final + { + enum class Load + { + Pre, + Post, + }; + + /** Properties group name. */ + std::string Name; + + /** Properties group load behavior. */ + Load Scope = Load::Post; + + /** Property key-value pairs in the group. */ + std::map Map; + }; + + /** Visual Studio major version number, if known. */ + cm::optional VSVersion; + + /** Whether this is a VS Express edition, if known. */ + cm::optional VSExpress; + + /** Solution-wide target platform. This is a Windows architecture. */ + std::string Platform; + + /** Solution-wide build configurations. + This corresponds to CMAKE_CONFIGURATION_TYPES. */ + std::vector Configs; + + /** List of all folders in the solution. */ + std::vector Folders; + + /** List of projects in the solution that are not in folders. */ + std::vector Projects; + + /** List of solution-level property groups. */ + std::vector PropertyGroups; + + /** Name of the default startup project. */ + std::string StartupProject; + + /** Get all projects in the solution, including all folders. */ + std::vector GetAllProjects() const; + + // Non-const methods used during creation. + Folder* GetFolder(cm::string_view name); + Project* GetProject(cm::string_view name); + PropertyGroup* GetPropertyGroup(cm::string_view name); + void CanonicalizeOrder(); + +private: + Solution(Solution const&) = delete; + Solution& operator=(Solution const&) = delete; + + // Own and index named entities. + // The string_view keys point at the Name members. + std::map> FolderMap; + std::map> ProjectMap; + std::map> PropertyGroupMap; +}; + +/** Write the .sln-format representation. */ +void WriteSln(std::ostream& sln, Solution const& solution); + +} +} diff --git a/Source/cmVSVersion.h b/Source/cmVSVersion.h index 3fe0d2fb72..756a06431b 100644 --- a/Source/cmVSVersion.h +++ b/Source/cmVSVersion.h @@ -15,5 +15,11 @@ enum class Version : std::uint16_t VS17 = 170, VS18 = 180, }; + +enum class VersionExpress +{ + No, + Yes, +}; } }