You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

216 lines
7.7 KiB

  1. // Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4. // software and associated documentation files (the "Software"), to deal in the Software
  5. // without restriction, including without limitation the rights to use, copy, modify, merge,
  6. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7. // to whom the Software is furnished to do so, subject to the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be included in all copies or
  10. // substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17. // DEALINGS IN THE SOFTWARE.
  18. using System;
  19. using System.Collections.Generic;
  20. using System.IO;
  21. using System.Linq;
  22. using System.Xml.Linq;
  23. namespace ICSharpCode.Decompiler.Solution
  24. {
  25. /// <summary>
  26. /// A helper class that can write a Visual Studio Solution file for the provided projects.
  27. /// </summary>
  28. public static class SolutionCreator
  29. {
  30. private static readonly XNamespace ProjectFileNamespace = XNamespace.Get("http://schemas.microsoft.com/developer/msbuild/2003");
  31. /// <summary>
  32. /// Writes a solution file to the specified <paramref name="targetFile"/>.
  33. /// </summary>
  34. /// <param name="targetFile">The full path of the file to write.</param>
  35. /// <param name="projects">The projects contained in this solution.</param>
  36. ///
  37. /// <exception cref="ArgumentException">Thrown when <paramref name="targetFile"/> is null or empty.</exception>
  38. /// <exception cref="ArgumentNullException">Thrown when <paramref name="projects"/> is null.</exception>
  39. /// <exception cref="InvalidOperationException">Thrown when <paramref name="projects"/> contains no items.</exception>
  40. public static void WriteSolutionFile(string targetFile, IEnumerable<ProjectItem> projects)
  41. {
  42. if (string.IsNullOrWhiteSpace(targetFile))
  43. {
  44. throw new ArgumentException("The target file cannot be null or empty.", nameof(targetFile));
  45. }
  46. if (projects == null)
  47. {
  48. throw new ArgumentNullException(nameof(projects));
  49. }
  50. if (!projects.Any())
  51. {
  52. throw new InvalidOperationException("At least one project is expected.");
  53. }
  54. using (var writer = new StreamWriter(targetFile))
  55. {
  56. WriteSolutionFile(writer, projects, targetFile);
  57. }
  58. FixProjectReferences(projects);
  59. }
  60. private static void WriteSolutionFile(TextWriter writer, IEnumerable<ProjectItem> projects, string solutionFilePath)
  61. {
  62. WriteHeader(writer);
  63. WriteProjects(writer, projects, solutionFilePath);
  64. writer.WriteLine("Global");
  65. var platforms = WriteSolutionConfigurations(writer, projects);
  66. WriteProjectConfigurations(writer, projects, platforms);
  67. writer.WriteLine("\tGlobalSection(SolutionProperties) = preSolution");
  68. writer.WriteLine("\t\tHideSolutionNode = FALSE");
  69. writer.WriteLine("\tEndGlobalSection");
  70. writer.WriteLine("EndGlobal");
  71. }
  72. private static void WriteHeader(TextWriter writer)
  73. {
  74. writer.WriteLine("Microsoft Visual Studio Solution File, Format Version 12.00");
  75. writer.WriteLine("# Visual Studio 14");
  76. writer.WriteLine("VisualStudioVersion = 14.0.24720.0");
  77. writer.WriteLine("MinimumVisualStudioVersion = 10.0.40219.1");
  78. }
  79. private static void WriteProjects(TextWriter writer, IEnumerable<ProjectItem> projects, string solutionFilePath)
  80. {
  81. foreach (var project in projects)
  82. {
  83. var projectRelativePath = GetRelativePath(solutionFilePath, project.FilePath);
  84. var typeGuid = project.TypeGuid.ToString("B").ToUpperInvariant();
  85. var projectGuid = project.Guid.ToString("B").ToUpperInvariant();
  86. writer.WriteLine($"Project(\"{typeGuid}\") = \"{project.ProjectName}\", \"{projectRelativePath}\", \"{projectGuid}\"");
  87. writer.WriteLine("EndProject");
  88. }
  89. }
  90. private static IEnumerable<string> WriteSolutionConfigurations(TextWriter writer, IEnumerable<ProjectItem> projects)
  91. {
  92. var platforms = projects.GroupBy(p => p.PlatformName).Select(g => g.Key).ToList();
  93. platforms.Sort();
  94. writer.WriteLine("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution");
  95. foreach (var platform in platforms)
  96. {
  97. writer.WriteLine($"\t\tDebug|{platform} = Debug|{platform}");
  98. }
  99. foreach (var platform in platforms)
  100. {
  101. writer.WriteLine($"\t\tRelease|{platform} = Release|{platform}");
  102. }
  103. writer.WriteLine("\tEndGlobalSection");
  104. return platforms;
  105. }
  106. private static void WriteProjectConfigurations(
  107. TextWriter writer,
  108. IEnumerable<ProjectItem> projects,
  109. IEnumerable<string> solutionPlatforms)
  110. {
  111. writer.WriteLine("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution");
  112. foreach (var project in projects)
  113. {
  114. var projectGuid = project.Guid.ToString("B").ToUpperInvariant();
  115. foreach (var platform in solutionPlatforms)
  116. {
  117. writer.WriteLine($"\t\t{projectGuid}.Debug|{platform}.ActiveCfg = Debug|{project.PlatformName}");
  118. writer.WriteLine($"\t\t{projectGuid}.Debug|{platform}.Build.0 = Debug|{project.PlatformName}");
  119. }
  120. foreach (var platform in solutionPlatforms)
  121. {
  122. writer.WriteLine($"\t\t{projectGuid}.Release|{platform}.ActiveCfg = Release|{project.PlatformName}");
  123. writer.WriteLine($"\t\t{projectGuid}.Release|{platform}.Build.0 = Release|{project.PlatformName}");
  124. }
  125. }
  126. writer.WriteLine("\tEndGlobalSection");
  127. }
  128. private static void FixProjectReferences(IEnumerable<ProjectItem> projects)
  129. {
  130. var projectsMap = projects.ToDictionary(p => p.ProjectName, p => p);
  131. foreach (var project in projects)
  132. {
  133. XDocument projectDoc = XDocument.Load(project.FilePath);
  134. var referencesItemGroups = projectDoc.Root
  135. .Elements(ProjectFileNamespace + "ItemGroup")
  136. .Where(e => e.Elements(ProjectFileNamespace + "Reference").Any());
  137. foreach (var itemGroup in referencesItemGroups)
  138. {
  139. FixProjectReferences(project.FilePath, itemGroup, projectsMap);
  140. }
  141. projectDoc.Save(project.FilePath);
  142. }
  143. }
  144. private static void FixProjectReferences(string projectFilePath, XElement itemGroup, IDictionary<string, ProjectItem> projects)
  145. {
  146. foreach (var item in itemGroup.Elements(ProjectFileNamespace + "Reference").ToList())
  147. {
  148. var assemblyName = item.Attribute("Include")?.Value;
  149. if (assemblyName != null && projects.TryGetValue(assemblyName, out var referencedProject))
  150. {
  151. item.Remove();
  152. var projectReference = new XElement(ProjectFileNamespace + "ProjectReference",
  153. new XElement(ProjectFileNamespace + "Project", referencedProject.Guid.ToString("B").ToUpperInvariant()),
  154. new XElement(ProjectFileNamespace + "Name", referencedProject.ProjectName));
  155. projectReference.SetAttributeValue("Include", GetRelativePath(projectFilePath, referencedProject.FilePath));
  156. itemGroup.Add(projectReference);
  157. }
  158. }
  159. }
  160. private static string GetRelativePath(string fromFilePath, string toFilePath)
  161. {
  162. Uri fromUri = new Uri(fromFilePath);
  163. Uri toUri = new Uri(toFilePath);
  164. if (fromUri.Scheme != toUri.Scheme)
  165. {
  166. return toFilePath;
  167. }
  168. Uri relativeUri = fromUri.MakeRelativeUri(toUri);
  169. string relativePath = Uri.UnescapeDataString(relativeUri.ToString());
  170. if (string.Equals(toUri.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase))
  171. {
  172. relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
  173. }
  174. return relativePath;
  175. }
  176. }
  177. }