blob: 2fc6fca63054839462bf22382612584577960446 [file] [log] [blame] [edit]
/* 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 "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmXMLWriter.h"
namespace cm {
namespace VS {
cm::string_view const Solution::Project::TypeIdCSharp =
"FAE04EC0-301F-11D3-BF4B-00C04F79EFBC"_s;
cm::string_view const Solution::Project::TypeIdDatabase =
"C8D11400-126E-41CD-887F-60BD40844F9E"_s;
cm::string_view const Solution::Project::TypeIdDefault =
"8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942"_s;
cm::string_view const Solution::Project::TypeIdFSharp =
"F2A71F9B-5D33-465A-A702-920D77279786"_s;
cm::string_view const Solution::Project::TypeIdFortran =
"6989167D-11E4-40FE-8C1A-2192A86A7E90"_s;
cm::string_view const Solution::Project::TypeIdPython =
"888888A0-9F3D-457C-B088-3A5042F75D52"_s;
cm::string_view const Solution::Project::TypeIdVDProj =
"54435603-DBB4-11D2-8724-00A0C9A8B90C"_s;
cm::string_view const Solution::Project::TypeIdVisualBasic =
"F184B08F-C81C-45F6-A57F-5ABD9991F28F"_s;
cm::string_view const Solution::Project::TypeIdWiX =
"930C7802-8A8C-48F9-8165-68863BCCD9DD"_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";
}
namespace {
void WriteSlnxSolutionConfigurationPlatforms(cmXMLElement& xmlParent,
Solution const& solution)
{
cmXMLElement xmlConfigurations(xmlParent, "Configurations");
for (std::string const& c : solution.Configs) {
cmXMLElement(xmlConfigurations, "BuildType").Attribute("Name", c);
}
cmXMLElement(xmlConfigurations, "Platform")
.Attribute("Name", solution.Platform);
};
void WriteSlnxProject(cmXMLElement& xmlParent, Solution const& solution,
Solution::Project const& project)
{
cmXMLElement xmlProject(xmlParent, "Project");
xmlProject.Attribute("Path", project.Path);
xmlProject.Attribute("Id", cmSystemTools::LowerCase(project.Id));
for (Solution::Project const* d : project.BuildDependencies) {
cmXMLElement(xmlProject, "BuildDependency").Attribute("Project", d->Path);
}
assert(project.Configs.size() == solution.Configs.size());
for (std::size_t i = 0; i < solution.Configs.size(); ++i) {
if (project.Configs[i].Config != solution.Configs[i]) {
cmXMLElement(xmlProject, "BuildType")
.Attribute("Project", project.Configs[i].Config);
}
if (!project.Configs[i].Build) {
cmXMLElement(xmlProject, "Build")
.Attribute("Solution", cmStrCat(solution.Configs[i], "|*"))
.Attribute("Project", "false");
}
if (project.Configs[i].Deploy) {
cmXMLElement(xmlProject, "Deploy")
.Attribute("Solution", cmStrCat(solution.Configs[i], "|*"));
}
}
if (project.Platform != solution.Platform) {
cmXMLElement(xmlProject, "Platform")
.Attribute("Project", project.Platform);
}
};
void WriteSlnxFolder(cmXMLElement& xmlParent, Solution const& solution,
Solution::Folder const& folder)
{
cmXMLElement xmlFolder(xmlParent, "Folder");
xmlFolder.Attribute("Name", cmStrCat('/', folder.Name, '/'));
for (std::string const& filePath : folder.Files) {
cmXMLElement(xmlFolder, "File").Attribute("Path", filePath);
}
for (Solution::Project const* project : folder.Projects) {
WriteSlnxProject(xmlFolder, solution, *project);
}
};
void WriteSlnxPropertyGroup(cmXMLElement& xmlParent,
Solution::PropertyGroup const& pg)
{
cmXMLElement xmlProperties(xmlParent, "Properties");
xmlProperties.Attribute("Name", pg.Name);
if (pg.Scope == Solution::PropertyGroup::Load::Post) {
xmlProperties.Attribute("Scope", "PostLoad");
}
for (auto const& i : pg.Map) {
cmXMLElement(xmlProperties, "Properties")
.Attribute("Name", i.first)
.Attribute("Value", i.second);
}
}
}
void WriteSlnx(std::ostream& slnx, Solution const& solution)
{
cmXMLWriter xw(slnx);
cmXMLDocument xml(xw);
cmXMLElement xmlSolution(xml, "Solution");
WriteSlnxSolutionConfigurationPlatforms(xmlSolution, solution);
for (Solution::Project const* project : solution.Projects) {
WriteSlnxProject(xmlSolution, solution, *project);
}
for (Solution::Folder const* folder : solution.Folders) {
WriteSlnxFolder(xmlSolution, solution, *folder);
}
for (Solution::PropertyGroup const* pg : solution.PropertyGroups) {
WriteSlnxPropertyGroup(xmlSolution, *pg);
}
}
}
}