cmVSSolution.cxx 13 KB

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