1
0
mirror of https://github.com/Kitware/CMake.git synced 2025-10-14 02:08:27 +08:00

VS: Decouple solution generation from .sln file format

Rewrite `.sln` generation using a structured intermediate representation.

Issue: #25887
This commit is contained in:
Brad King
2025-09-15 11:57:39 -04:00
parent d67e7d2726
commit 3882718872
16 changed files with 716 additions and 705 deletions

View File

@@ -975,6 +975,8 @@ if(WIN32)
cmVisualStudioWCEPlatformParser.cxx
cmVSSetupHelper.cxx
cmVSSetupHelper.h
cmVSSolution.cxx
cmVSSolution.h
cmVSVersion.h
)

View File

@@ -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<cmSourceGroup> sourceGroups = makefile->GetSourceGroups();
cmVisualStudioFolder* defaultFolder = nullptr;
std::vector<std::string> 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";
}

View File

@@ -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;

View File

@@ -2,167 +2,9 @@
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmGlobalVisualStudio71Generator.h"
#include <map>
#include <sstream>
#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<std::string> 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<BT<std::pair<std::string, bool>>> 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<std::pair<std::string, bool>> 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<std::string> const& configs,
std::set<std::string> 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;
}
}
}

View File

@@ -2,49 +2,12 @@
file LICENSE.rst or https://cmake.org/licensing for details. */
#pragma once
#include <iosfwd>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "cmGlobalVisualStudio7Generator.h"
#include "cmValue.h"
class cmGeneratorTarget;
class cmLocalGenerator;
class cmake;
template <typename T>
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<std::string> 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<std::string> const& configs,
std::set<std::string> 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<BT<std::pair<std::string, bool>>> const& depends) const override;
std::string ProjectConfigurationSectionName;
};

View File

@@ -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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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)

View File

@@ -25,12 +25,6 @@ class cmake;
template <typename T>
class BT;
struct cmVisualStudioFolder
{
std::set<std::string> Projects;
std::set<std::string> SolutionItems;
};
/** \class cmGlobalVisualStudio7Generator
* \brief Write a Unix makefiles.
*
@@ -126,12 +120,6 @@ protected:
void Generate() override;
struct VSFolders
{
std::map<std::string, cmVisualStudioFolder> 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<cmLocalGenerator*>& 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<std::string> const& configs,
std::set<std::string> 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<std::string> const& configs,
OrderedTargetDependSet const& projectTargets) const;
virtual void WriteExternalProject(
std::ostream& fout, std::string const& name, std::string const& path,
cmValue typeGuid,
std::set<BT<std::pair<std::string, bool>>> 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;

View File

@@ -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<std::string> 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<std::string> const& configs,
std::set<std::string> 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)
{

View File

@@ -70,17 +70,6 @@ protected:
bool TargetSystemSupportsDeployment() const override;
static cmIDEFlagTable const* GetExtraFlagTableVS8();
void WriteSolutionConfigurations(
std::ostream& fout,
std::vector<std::string> const& configs) const override;
void WriteProjectConfigurations(
std::ostream& fout, std::string const& name,
cmGeneratorTarget const& target, std::vector<std::string> const& configs,
std::set<std::string> 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;

View File

@@ -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<std::string> cmGlobalVisualStudioGenerator::IsPartOfDefaultBuild(
std::vector<std::string> const& configs,
OrderedTargetDependSet const& projectTargets,
cmGeneratorTarget const* target) const
TargetDependSet const& projectTargets, cmGeneratorTarget const* target) const
{
std::set<std::string> 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<std::string> 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<std::pair<std::string, bool>> 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<cmSourceGroup> sourceGroups = mf->GetSourceGroups();
std::vector<std::string> 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<std::string> 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);
}

View File

@@ -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<std::string> IsPartOfDefaultBuild(
std::vector<std::string> 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<std::string, std::string> 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&,

View File

@@ -8,6 +8,7 @@
#include <utility>
#include <vector>
#include <cm/optional>
#include <cmext/string_view>
#include "cmsys/FStream.hxx"

View File

@@ -8,6 +8,7 @@
#include <string>
#include <cm/optional>
#include <cm/string_view>
#include "cmGlobalVisualStudio10Generator.h"
#include "cmGlobalVisualStudio14Generator.h"

283
Source/cmVSSolution.cxx Normal file
View File

@@ -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 <algorithm>
#include <cassert>
#include <iostream>
#include <map>
#include <cm/string_view>
#include <cmext/string_view>
#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::Project const*> Solution::GetAllProjects() const
{
std::vector<Project const*> 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 <typename T>
T* GetEntry(std::map<cm::string_view, std::unique_ptr<T>>& entryMap,
cm::string_view name)
{
auto i = entryMap.find(name);
if (i == entryMap.end()) {
auto p = cm::make_unique<T>();
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 <typename T>
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<Solution::Project const*> 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<Solution::Folder const*> 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<Solution::Project const*> 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";
}
}
}

169
Source/cmVSSolution.h Normal file
View File

@@ -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 <map>
#include <set>
#include <string>
#include <vector>
#include <cm/memory>
#include <cm/optional>
#include <cm/string_view>
#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<ProjectConfig> Configs;
/** Solution-level dependencies of the project on other projects. */
std::vector<Project const*> 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<Folder const*> Folders;
/** List of projects contained inside this folder. */
std::vector<Project const*> Projects;
/** Solution-level files contained inside this folder. */
std::set<std::string> 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<std::string, std::string> Map;
};
/** Visual Studio major version number, if known. */
cm::optional<Version> VSVersion;
/** Whether this is a VS Express edition, if known. */
cm::optional<VersionExpress> 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<std::string> Configs;
/** List of all folders in the solution. */
std::vector<Folder const*> Folders;
/** List of projects in the solution that are not in folders. */
std::vector<Project const*> Projects;
/** List of solution-level property groups. */
std::vector<PropertyGroup const*> PropertyGroups;
/** Name of the default startup project. */
std::string StartupProject;
/** Get all projects in the solution, including all folders. */
std::vector<Project const*> 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<cm::string_view, std::unique_ptr<Folder>> FolderMap;
std::map<cm::string_view, std::unique_ptr<Project>> ProjectMap;
std::map<cm::string_view, std::unique_ptr<PropertyGroup>> PropertyGroupMap;
};
/** Write the .sln-format representation. */
void WriteSln(std::ostream& sln, Solution const& solution);
}
}

View File

@@ -15,5 +15,11 @@ enum class Version : std::uint16_t
VS17 = 170,
VS18 = 180,
};
enum class VersionExpress
{
No,
Yes,
};
}
}