cmVSSolution.cxx 13 KB

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