cmVSSolution.cxx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file LICENSE.rst or https://cmake.org/licensing for details. */
  3. #include "cmVSSolution.h"
  4. #include <algorithm>
  5. #include <cassert>
  6. #include <iostream>
  7. #include <map>
  8. #include <cm/string_view>
  9. #include <cmext/string_view>
  10. #include "cmStringAlgorithms.h"
  11. #include "cmSystemTools.h"
  12. #include "cmXMLWriter.h"
  13. namespace cm {
  14. namespace VS {
  15. cm::string_view const Solution::Project::TypeIdCSharp =
  16. "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC"_s;
  17. cm::string_view const Solution::Project::TypeIdDatabase =
  18. "C8D11400-126E-41CD-887F-60BD40844F9E"_s;
  19. cm::string_view const Solution::Project::TypeIdDefault =
  20. "8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942"_s;
  21. cm::string_view const Solution::Project::TypeIdFSharp =
  22. "F2A71F9B-5D33-465A-A702-920D77279786"_s;
  23. cm::string_view const Solution::Project::TypeIdFortran =
  24. "6989167D-11E4-40FE-8C1A-2192A86A7E90"_s;
  25. cm::string_view const Solution::Project::TypeIdPython =
  26. "888888A0-9F3D-457C-B088-3A5042F75D52"_s;
  27. cm::string_view const Solution::Project::TypeIdVDProj =
  28. "54435603-DBB4-11D2-8724-00A0C9A8B90C"_s;
  29. cm::string_view const Solution::Project::TypeIdVisualBasic =
  30. "F184B08F-C81C-45F6-A57F-5ABD9991F28F"_s;
  31. cm::string_view const Solution::Project::TypeIdWiX =
  32. "930C7802-8A8C-48F9-8165-68863BCCD9DD"_s;
  33. cm::string_view const Solution::Folder::TypeId =
  34. "2150E333-8FDC-42A3-9474-1A3956D46DE8"_s;
  35. std::vector<Solution::Project const*> Solution::GetAllProjects() const
  36. {
  37. std::vector<Project const*> projects;
  38. projects.reserve(this->ProjectMap.size());
  39. for (Project const* project : this->Projects) {
  40. projects.emplace_back(project);
  41. }
  42. for (Folder const* folder : this->Folders) {
  43. for (Project const* project : folder->Projects) {
  44. projects.emplace_back(project);
  45. }
  46. }
  47. return projects;
  48. }
  49. namespace {
  50. template <typename T>
  51. T* GetEntry(std::map<cm::string_view, std::unique_ptr<T>>& entryMap,
  52. cm::string_view name)
  53. {
  54. auto i = entryMap.find(name);
  55. if (i == entryMap.end()) {
  56. auto p = cm::make_unique<T>();
  57. p->Name = name;
  58. i = entryMap.emplace(p->Name, std::move(p)).first;
  59. }
  60. return i->second.get();
  61. }
  62. }
  63. Solution::Folder* Solution::GetFolder(cm::string_view name)
  64. {
  65. return GetEntry(this->FolderMap, name);
  66. }
  67. Solution::Project* Solution::GetProject(cm::string_view name)
  68. {
  69. return GetEntry(this->ProjectMap, name);
  70. }
  71. Solution::PropertyGroup* Solution::GetPropertyGroup(cm::string_view name)
  72. {
  73. return GetEntry(this->PropertyGroupMap, name);
  74. }
  75. namespace {
  76. struct OrderByName
  77. {
  78. template <typename T>
  79. bool operator()(T const* l, T const* r) const
  80. {
  81. return l->Name < r->Name;
  82. }
  83. };
  84. }
  85. void Solution::CanonicalizeOrder()
  86. {
  87. std::sort(this->Folders.begin(), this->Folders.end(), OrderByName());
  88. for (auto& fi : this->FolderMap) {
  89. Folder* folder = fi.second.get();
  90. std::sort(folder->Folders.begin(), folder->Folders.end(), OrderByName());
  91. std::sort(folder->Projects.begin(), folder->Projects.end(), OrderByName());
  92. }
  93. std::sort(this->Projects.begin(), this->Projects.end(), OrderByName());
  94. for (auto& pi : this->ProjectMap) {
  95. Project* project = pi.second.get();
  96. std::sort(project->BuildDependencies.begin(),
  97. project->BuildDependencies.end(), OrderByName());
  98. }
  99. }
  100. namespace {
  101. void WriteSlnHeader(std::ostream& sln, Version version, VersionExpress express)
  102. {
  103. char utf8bom[] = { char(0xEF), char(0xBB), char(0xBF) };
  104. sln.write(utf8bom, 3);
  105. sln << '\n';
  106. switch (version) {
  107. case Version::VS14:
  108. // Visual Studio 14 writes .sln format 12.00
  109. sln << "Microsoft Visual Studio Solution File, Format Version 12.00\n";
  110. if (express == VersionExpress::Yes) {
  111. sln << "# Visual Studio Express 14 for Windows Desktop\n";
  112. } else {
  113. sln << "# Visual Studio 14\n";
  114. }
  115. break;
  116. case Version::VS15:
  117. // Visual Studio 15 writes .sln format 12.00
  118. sln << "Microsoft Visual Studio Solution File, Format Version 12.00\n";
  119. sln << "# Visual Studio 15\n";
  120. break;
  121. case Version::VS16:
  122. // Visual Studio 16 writes .sln format 12.00
  123. sln << "Microsoft Visual Studio Solution File, Format Version 12.00\n";
  124. sln << "# Visual Studio Version 16\n";
  125. break;
  126. case Version::VS17:
  127. // Visual Studio 17 writes .sln format 12.00
  128. sln << "Microsoft Visual Studio Solution File, Format Version 12.00\n";
  129. sln << "# Visual Studio Version 17\n";
  130. break;
  131. case Version::VS18:
  132. // Visual Studio 18 writes .sln format 12.00
  133. sln << "Microsoft Visual Studio Solution File, Format Version 12.00\n";
  134. sln << "# Visual Studio Version 18\n";
  135. break;
  136. }
  137. }
  138. void WriteSlnProject(std::ostream& sln, Solution::Project const& project)
  139. {
  140. std::string projectPath = project.Path;
  141. std::replace(projectPath.begin(), projectPath.end(), '/', '\\');
  142. sln << "Project(\"{" << project.TypeId << "}\") = \"" << project.Name
  143. << "\", \"" << projectPath << "\", \"{" << project.Id << "}\"\n";
  144. sln << "\tProjectSection(ProjectDependencies) = postProject\n";
  145. for (Solution::Project const* d : project.BuildDependencies) {
  146. sln << "\t\t{" << d->Id << "} = {" << d->Id << "}\n";
  147. }
  148. sln << "\tEndProjectSection\n";
  149. sln << "EndProject\n";
  150. }
  151. void WriteSlnFolder(std::ostream& sln, Solution::Folder const& folder)
  152. {
  153. std::string folderName = folder.Name;
  154. std::replace(folderName.begin(), folderName.end(), '/', '\\');
  155. std::string const fileName = cmSystemTools::GetFilenameName(folder.Name);
  156. sln << "Project(\"{" << Solution::Folder::TypeId << "}\") = \"" << fileName
  157. << "\", \"" << folderName << "\", \"{" << folder.Id << "}\"\n";
  158. if (!folder.Files.empty()) {
  159. sln << "\tProjectSection(SolutionItems) = preProject\n";
  160. for (std::string const& item : folder.Files) {
  161. sln << "\t\t" << item << " = " << item << "\n";
  162. }
  163. sln << "\tEndProjectSection\n";
  164. }
  165. sln << "EndProject\n";
  166. }
  167. void WriteSlnSolutionConfigurationPlatforms(std::ostream& sln,
  168. Solution const& solution)
  169. {
  170. sln << "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n";
  171. for (std::string const& config : solution.Configs) {
  172. sln << "\t\t" << config << '|' << solution.Platform << " = " << config
  173. << '|' << solution.Platform << '\n';
  174. }
  175. sln << "\tEndGlobalSection\n";
  176. }
  177. void WriteSlnProjectConfigurationPlatforms(std::ostream& sln,
  178. Solution const& solution,
  179. Solution::Project const& project)
  180. {
  181. auto const writeStep = [&sln, &solution, &project](std::size_t i,
  182. cm::string_view step) {
  183. sln << "\t\t{" << project.Id << "}." << solution.Configs[i] << '|'
  184. << solution.Platform << "." << step << " = "
  185. << project.Configs[i].Config << '|' << project.Platform << '\n';
  186. };
  187. assert(project.Configs.size() == solution.Configs.size());
  188. for (std::size_t i = 0; i < solution.Configs.size(); ++i) {
  189. writeStep(i, "ActiveCfg"_s);
  190. if (project.Configs[i].Build) {
  191. writeStep(i, "Build.0"_s);
  192. }
  193. if (project.Configs[i].Deploy) {
  194. writeStep(i, "Deploy.0"_s);
  195. }
  196. }
  197. }
  198. void WriteSlnProjectConfigurationPlatforms(
  199. std::ostream& sln, Solution const& solution,
  200. std::vector<Solution::Project const*> const& projects)
  201. {
  202. sln << "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n";
  203. for (Solution::Project const* project : projects) {
  204. WriteSlnProjectConfigurationPlatforms(sln, solution, *project);
  205. }
  206. sln << "\tEndGlobalSection\n";
  207. }
  208. void WriteSlnNestedProjects(
  209. std::ostream& sln, std::vector<Solution::Folder const*> const& folders)
  210. {
  211. sln << "\tGlobalSection(NestedProjects) = preSolution\n";
  212. for (Solution::Folder const* folder : folders) {
  213. for (Solution::Folder const* nestedFolder : folder->Folders) {
  214. sln << "\t\t{" << nestedFolder->Id << "} = {" << folder->Id << "}\n";
  215. }
  216. for (Solution::Project const* project : folder->Projects) {
  217. sln << "\t\t{" << project->Id << "} = {" << folder->Id << "}\n";
  218. }
  219. }
  220. sln << "\tEndGlobalSection\n";
  221. }
  222. void WriteSlnPropertyGroup(std::ostream& sln,
  223. Solution::PropertyGroup const& pg)
  224. {
  225. cm::string_view const order = pg.Scope == Solution::PropertyGroup::Load::Pre
  226. ? "preSolution"_s
  227. : "postSolution"_s;
  228. sln << "\tGlobalSection(" << pg.Name << ") = " << order << '\n';
  229. for (auto const& i : pg.Map) {
  230. sln << "\t\t" << i.first << " = " << i.second << '\n';
  231. }
  232. sln << "\tEndGlobalSection\n";
  233. }
  234. }
  235. void WriteSln(std::ostream& sln, Solution const& solution)
  236. {
  237. assert(solution.VSVersion);
  238. assert(solution.VSExpress);
  239. std::vector<Solution::Project const*> projects = solution.GetAllProjects();
  240. std::sort(projects.begin(), projects.end(),
  241. [&solution](Solution::Project const* l,
  242. Solution::Project const* r) -> bool {
  243. if (r->Name == solution.StartupProject) {
  244. return false;
  245. }
  246. if (l->Name == solution.StartupProject) {
  247. return true;
  248. }
  249. return l->Name < r->Name;
  250. });
  251. WriteSlnHeader(sln, *solution.VSVersion, *solution.VSExpress);
  252. for (Solution::Folder const* folder : solution.Folders) {
  253. WriteSlnFolder(sln, *folder);
  254. }
  255. for (Solution::Project const* project : projects) {
  256. WriteSlnProject(sln, *project);
  257. }
  258. sln << "Global\n";
  259. WriteSlnSolutionConfigurationPlatforms(sln, solution);
  260. WriteSlnProjectConfigurationPlatforms(sln, solution, projects);
  261. if (!solution.Folders.empty()) {
  262. WriteSlnNestedProjects(sln, solution.Folders);
  263. }
  264. for (Solution::PropertyGroup const* pg : solution.PropertyGroups) {
  265. WriteSlnPropertyGroup(sln, *pg);
  266. }
  267. sln << "EndGlobal\n";
  268. }
  269. namespace {
  270. void WriteSlnxSolutionConfigurationPlatforms(cmXMLElement& xmlParent,
  271. Solution const& solution)
  272. {
  273. cmXMLElement xmlConfigurations(xmlParent, "Configurations");
  274. for (std::string const& c : solution.Configs) {
  275. cmXMLElement(xmlConfigurations, "BuildType").Attribute("Name", c);
  276. }
  277. cmXMLElement(xmlConfigurations, "Platform")
  278. .Attribute("Name", solution.Platform);
  279. };
  280. void WriteSlnxProject(cmXMLElement& xmlParent, Solution const& solution,
  281. Solution::Project const& project)
  282. {
  283. cmXMLElement xmlProject(xmlParent, "Project");
  284. xmlProject.Attribute("Path", project.Path);
  285. xmlProject.Attribute("Id", cmSystemTools::LowerCase(project.Id));
  286. for (Solution::Project const* d : project.BuildDependencies) {
  287. cmXMLElement(xmlProject, "BuildDependency").Attribute("Project", d->Path);
  288. }
  289. assert(project.Configs.size() == solution.Configs.size());
  290. for (std::size_t i = 0; i < solution.Configs.size(); ++i) {
  291. if (project.Configs[i].Config != solution.Configs[i]) {
  292. cmXMLElement(xmlProject, "BuildType")
  293. .Attribute("Project", project.Configs[i].Config);
  294. }
  295. if (!project.Configs[i].Build) {
  296. cmXMLElement(xmlProject, "Build")
  297. .Attribute("Solution", cmStrCat(solution.Configs[i], "|*"))
  298. .Attribute("Project", "false");
  299. }
  300. if (project.Configs[i].Deploy) {
  301. cmXMLElement(xmlProject, "Deploy")
  302. .Attribute("Solution", cmStrCat(solution.Configs[i], "|*"));
  303. }
  304. }
  305. if (project.Platform != solution.Platform ||
  306. // C# projects do not build interactively in the VS IDE unless they
  307. // have an explicit platform, even if it matches the SLN platform.
  308. project.TypeId == Solution::Project::TypeIdCSharp) {
  309. cmXMLElement(xmlProject, "Platform")
  310. .Attribute("Project", project.Platform);
  311. }
  312. };
  313. void WriteSlnxFolder(cmXMLElement& xmlParent, Solution const& solution,
  314. Solution::Folder const& folder)
  315. {
  316. cmXMLElement xmlFolder(xmlParent, "Folder");
  317. xmlFolder.Attribute("Name", cmStrCat('/', folder.Name, '/'));
  318. for (std::string const& filePath : folder.Files) {
  319. cmXMLElement(xmlFolder, "File").Attribute("Path", filePath);
  320. }
  321. for (Solution::Project const* project : folder.Projects) {
  322. WriteSlnxProject(xmlFolder, solution, *project);
  323. }
  324. };
  325. void WriteSlnxPropertyGroup(cmXMLElement& xmlParent,
  326. Solution::PropertyGroup const& pg)
  327. {
  328. cmXMLElement xmlProperties(xmlParent, "Properties");
  329. xmlProperties.Attribute("Name", pg.Name);
  330. if (pg.Scope == Solution::PropertyGroup::Load::Post) {
  331. xmlProperties.Attribute("Scope", "PostLoad");
  332. }
  333. for (auto const& i : pg.Map) {
  334. cmXMLElement(xmlProperties, "Properties")
  335. .Attribute("Name", i.first)
  336. .Attribute("Value", i.second);
  337. }
  338. }
  339. }
  340. void WriteSlnx(std::ostream& slnx, Solution const& solution)
  341. {
  342. cmXMLWriter xw(slnx);
  343. cmXMLDocument xml(xw);
  344. cmXMLElement xmlSolution(xml, "Solution");
  345. WriteSlnxSolutionConfigurationPlatforms(xmlSolution, solution);
  346. for (Solution::Project const* project : solution.Projects) {
  347. WriteSlnxProject(xmlSolution, solution, *project);
  348. }
  349. for (Solution::Folder const* folder : solution.Folders) {
  350. WriteSlnxFolder(xmlSolution, solution, *folder);
  351. }
  352. for (Solution::PropertyGroup const* pg : solution.PropertyGroups) {
  353. WriteSlnxPropertyGroup(xmlSolution, *pg);
  354. }
  355. }
  356. }
  357. }