Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
PackageSessionHelper.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.IO;
6 using System.Linq;
7 using SiliconStudio.Core;
8 using SiliconStudio.Core.Diagnostics;
9 using SiliconStudio.Core.IO;
10 using SiliconStudio.Core.VisualStudio;
11 
12 namespace SiliconStudio.Assets
13 {
14  /// <summary>
15  /// Helper class to load/save a VisualStudio solution.
16  /// </summary>
17  internal class PackageSessionHelper
18  {
19  private const string SiliconStudioPackage = "SiliconStudioPackage";
20  private const string SolutionHeader = @"Microsoft Visual Studio Solution File, Format Version 12.00
21 # Visual Studio 2013
22 VisualStudioVersion = 12.0.30723.0
23 MinimumVisualStudioVersion = 10.0.40219.1";
24 
25  public static bool IsSolutionFile(string filePath)
26  {
27  return String.Compare(Path.GetExtension(filePath), ".sln", StringComparison.InvariantCultureIgnoreCase) == 0;
28  }
29 
30  public static bool IsPackageFile(string filePath)
31  {
32  return String.Compare(Path.GetExtension(filePath), Package.PackageFileExtension, StringComparison.InvariantCultureIgnoreCase) == 0;
33  }
34 
35  public static void LoadSolution(PackageSession session, string filePath, List<string> packagePaths, ILogger sessionResult)
36  {
37  var solutionDirectory = Path.GetDirectoryName(filePath);
38  if (solutionDirectory == null)
39  {
40  throw new ArgumentException("Must be absolute", "filePath");
41  }
42 
43  // The session should save back its changes to the solution
44  session.SolutionPath = filePath;
45 
46  var solution = Solution.FromFile(filePath);
47 
48  foreach (var project in solution.Projects)
49  {
50  string packagePath;
51  if (IsPackage(project, out packagePath))
52  {
53  var packageFullPath = Path.Combine(solutionDirectory, packagePath);
54  packagePaths.Add(packageFullPath);
55  }
56  }
57  }
58 
59  private static bool IsPackage(Project project)
60  {
61  string packagePath;
62  return IsPackage(project, out packagePath);
63  }
64 
65  private static bool IsPackage(Project project, out string packagePathRelative)
66  {
67  packagePathRelative = null;
68  if (project.IsSolutionFolder && project.Sections.Contains(SiliconStudioPackage))
69  {
70  var propertyItem = project.Sections[SiliconStudioPackage].Properties.FirstOrDefault();
71  if (propertyItem != null)
72  {
73  packagePathRelative = propertyItem.Name;
74  return true;
75  }
76  }
77  return false;
78  }
79 
80  public static void SaveSolution(PackageSession session, ILogger log)
81  {
82  // If the solution path is not set, we can't save a solution (sln)
83  if (session.SolutionPath == null)
84  {
85  return;
86  }
87 
88  var solutionPath = UPath.Combine(Environment.CurrentDirectory, session.SolutionPath);
89 
90  try
91  {
92  Solution solution;
93 
94  var solutionDir = solutionPath.GetParent();
95 
96  // If the solution already exists, we need to update it
97  if (File.Exists(solutionPath))
98  {
99  solution = Solution.FromFile(solutionPath);
100  }
101  else
102  {
103  solution = new Solution { FullPath = solutionPath };
104  solution.Headers.Add(SolutionHeader);
105  }
106 
107  // Pre-create solution wide global sections
108  if (!solution.GlobalSections.Contains("SolutionConfigurationPlatforms"))
109  {
110  solution.GlobalSections.Add(new Section("SolutionConfigurationPlatforms", "GlobalSection", "preSolution", Enumerable.Empty<PropertyItem>()));
111  }
112  if (!solution.GlobalSections.Contains("ProjectConfigurationPlatforms"))
113  {
114  solution.GlobalSections.Add(new Section("ProjectConfigurationPlatforms", "GlobalSection", "postSolution", Enumerable.Empty<PropertyItem>()));
115  }
116  if (!solution.GlobalSections.Contains("NestedProjects"))
117  {
118  solution.GlobalSections.Add(new Section("NestedProjects", "GlobalSection", "preSolution", Enumerable.Empty<PropertyItem>()));
119  }
120 
121  // ---------------------------------------------
122  // 0. Pre-select only platforms effectively used by this session
123  // ---------------------------------------------
124  var platformsUsedBySession = new SolutionPlatformCollection();
125  platformsUsedBySession.AddRange(AssetRegistry.SupportedPlatforms.Where(platform => platform.Type == PlatformType.Windows));
126 
127  foreach (var package in session.LocalPackages)
128  {
129  foreach (var profile in package.Profiles.Where(profile => profile.Platform != PlatformType.Shared && profile.ProjectReferences.Count > 0))
130  {
131  var platformType = profile.Platform;
132  if (!platformsUsedBySession.Contains(platformType))
133  {
134  platformsUsedBySession.AddRange(AssetRegistry.SupportedPlatforms.Where(platform => platform.Type == platformType));
135  }
136  }
137  }
138 
139  // ---------------------------------------------
140  // 1. Update configuration/platform
141  // ---------------------------------------------
142  var configs = new List<Tuple<string, SolutionPlatform, SolutionPlatformPart>>();
143  foreach (var configName in platformsUsedBySession.SelectMany(solutionPlatform => solutionPlatform.Configurations).Select(config => config.Name).Distinct())
144  {
145  foreach (var platform in platformsUsedBySession)
146  {
147  foreach (var platformPart in platform.GetParts())
148  {
149  // Skip platforms with IncludeInSolution == false
150  if (!platformPart.IncludeInSolution)
151  continue;
152 
153  configs.Add(new Tuple<string, SolutionPlatform, SolutionPlatformPart>(configName, platform, platformPart));
154  }
155  }
156  }
157 
158  // Order per config and then per platform names
159  configs = configs.OrderBy(part => part.Item1, StringComparer.InvariantCultureIgnoreCase).ThenBy(part => part.Item3.SafeSolutionName, StringComparer.InvariantCultureIgnoreCase).ToList();
160 
161  // Write configs in alphabetical order to avoid changes in sln after it is generated
162  var solutionPlatforms = solution.GlobalSections["SolutionConfigurationPlatforms"];
163  solutionPlatforms.Properties.Clear();
164  foreach (var config in configs)
165  {
166  var solutionConfigPlatform = string.Format("{0}|{1}", config.Item1, config.Item3.SafeSolutionName);
167  if (!solutionPlatforms.Properties.Contains(solutionConfigPlatform))
168  {
169  solutionPlatforms.Properties.Add(new PropertyItem(solutionConfigPlatform, solutionConfigPlatform));
170  }
171  }
172 
173  // Remove projects that are no longer available on the disk
174  var projectToRemove = solution.Projects.Where(project => !project.IsSolutionFolder && !File.Exists(project.FullPath)).ToList();
175  foreach (var project in projectToRemove)
176  {
177  solution.Projects.Remove(project);
178  }
179 
180  // ---------------------------------------------
181  // 2. Update each package
182  // ---------------------------------------------
183  foreach (var package in session.LocalPackages)
184  {
185  if (string.IsNullOrWhiteSpace(package.Meta.Name))
186  {
187  log.Error("Error while saving solution [{0}]. Package [{1}] should have a Meta.Name", solutionPath, package.FullPath);
188  continue;
189  }
190 
191  var packageFolder = solution.Projects.FindByGuid(package.Id);
192 
193  // Packages are created as solution folders in VisualStudio
194  if (packageFolder == null)
195  {
196  // Create this package as a Solution Folder
197  packageFolder = new Project(solution,
198  package.Id,
200  package.Meta.Name,
201  package.Meta.Name,
202  Guid.Empty,
203  Enumerable.Empty<Section>(),
204  Enumerable.Empty<PropertyItem>(),
205  Enumerable.Empty<PropertyItem>());
206 
207  // As it is making a copy, we need to get it back
208  solution.Projects.Add(packageFolder);
209  packageFolder = solution.Projects[package.Id];
210  }
211 
212  // Update the path to the solution everytime we save a package
213  packageFolder.Sections.Clear();
214  var relativeUrl = package.FullPath.MakeRelative(solutionDir);
215  packageFolder.Sections.Add(new Section(SiliconStudioPackage, "ProjectSection", "preProject", new[] { new PropertyItem(relativeUrl, relativeUrl) }));
216 
217  // ---------------------------------------------
218  // 2.1. Update each project
219  // ---------------------------------------------
220  foreach (var profile in package.Profiles.OrderBy(x => x.Platform == PlatformType.Windows ? 0 : 1))
221  {
222  foreach (var project in profile.ProjectReferences)
223  {
224  var projectInSolution = solution.Projects.FindByGuid(project.Id);
225 
226  if (projectInSolution == null)
227  {
228  var projectRelativePath = project.Location.MakeRelative(solutionDir);
229 
230  // Create this package as a Solution Folder
231  projectInSolution = new Project(solution,
232  project.Id,
234  project.Location.GetFileName(),
235  projectRelativePath.ToWindowsPath(),
236  package.Id,
237  Enumerable.Empty<Section>(),
238  Enumerable.Empty<PropertyItem>(),
239  Enumerable.Empty<PropertyItem>());
240 
241  solution.Projects.Add(projectInSolution);
242 
243  // Resolve it again, as the original code is making a clone of it (why?)
244  projectInSolution = solution.Projects.FindByGuid(project.Id);
245  }
246 
247  // Projects are always in a package (solution folder) in the solution
248  projectInSolution.ParentGuid = package.Id;
249 
250  // Update platforms per project (active solution and build flag per platform)
251 
252  // Clear all platform properties for this project and recompute them here
253  projectInSolution.PlatformProperties.Clear();
254 
255  foreach (var config in configs)
256  {
257  var configName = config.Item1;
258  var platform = config.Item2;
259  var platformPart = config.Item3;
260 
261  // Filter exe project types
262  if (project.Type == ProjectType.Executable && !platformPart.UseWithExecutables)
263  {
264  continue;
265  }
266 
267  var platformName = platformPart.SafeSolutionName;
268 
269  var solutionConfigPlatform = string.Format("{0}|{1}", configName, platformName);
270 
271  var configNameInProject = configName;
272  var platformNameInProject = platformPart.GetProjectName(project.Type);
273 
274  var platformTarget = platform;
275  if (profile.Platform != PlatformType.Shared)
276  {
277  platformTarget = platformsUsedBySession.First(plat => plat.Type == profile.Platform);
278  }
279 
280  bool isPartOfBuild = platformTarget == platform;
281  // If the config doesn't exist for this platform, just use the default config name
282  if (!platformTarget.Configurations.Contains(configName))
283  {
284  configNameInProject = platformTarget.Configurations.FirstOrDefault().Name;
285  isPartOfBuild = false;
286  }
287 
288  // If the config doesn't exist for this platform, just use the default config name
289  if (platformTarget.GetParts().All(part => part.GetProjectName(project.Type) != platformNameInProject))
290  {
291  platformNameInProject = platformTarget.GetParts().FirstOrDefault(part => part.IsProjectHandled(project.Type)).SafeSolutionName;
292  isPartOfBuild = false;
293  }
294 
295  var projectConfigPlatform = string.Format("{0}|{1}", configNameInProject, platformNameInProject);
296 
297  var propertyActive = solutionConfigPlatform + ".ActiveCfg";
298  var propertyBuild = solutionConfigPlatform + ".Build.0";
299 
300  if (!projectInSolution.PlatformProperties.Contains(propertyActive))
301  {
302  projectInSolution.PlatformProperties.Remove(propertyActive);
303  projectInSolution.PlatformProperties.Add(new PropertyItem(propertyActive, projectConfigPlatform));
304  }
305 
306  // Only add Build and Deploy for supported configs
307  if (isPartOfBuild)
308  {
309  projectInSolution.PlatformProperties.Remove(propertyBuild);
310  projectInSolution.PlatformProperties.Add(new PropertyItem(propertyBuild, projectConfigPlatform));
311 
312  // If the project is an executable, mark it as deploy
313  if (project.Type == ProjectType.Executable)
314  {
315  var propertyDeploy = solutionConfigPlatform + ".Deploy.0";
316  projectInSolution.PlatformProperties.Remove(propertyDeploy);
317  projectInSolution.PlatformProperties.Add(new PropertyItem(propertyDeploy, projectConfigPlatform));
318  }
319  }
320  }
321  }
322  }
323 
324  // ---------------------------------------------
325  // 3. Remove unused packages from the solution
326  // ---------------------------------------------
327  for (int i = solution.Projects.Count - 1; i >=0; i--)
328  {
329  var project = solution.Projects[i];
330  if (IsPackage(project) && !session.Packages.ContainsById(project.Guid))
331  {
332  solution.Projects.RemoveAt(i);
333  }
334  }
335  }
336 
337  solution.Save();
338  }
339  catch (Exception ex)
340  {
341  log.Error("Error while saving solution [{0}]", ex, solutionPath);
342  }
343  }
344  }
345 }
PlatformType
Describes the platform operating system.
Definition: PlatformType.cs:9
A project referenced by a VisualStudio solution.
Definition: Project.cs:35
bool IsSolutionFolder
Gets a value indicating whether this instance is solution folder.
Definition: Project.cs:134
A section defined in a Project
Definition: Section.cs:34
ProjectCollection Projects
Gets all projects.
Definition: Solution.cs:124
The template can be applied to an existing Assets.Package.
A key/value pair used by PropertyItemCollection
Definition: PropertyItem.cs:33
SectionCollection Sections
Gets the project sections.
Definition: Project.cs:418
System.IO.File File
A VisualStudio solution.
Definition: Solution.cs:37
EnvDTE.Project Project
ProjectType
Type of the project.
Definition: ProjectType.cs:11
Interface for logging.
Definition: ILogger.cs:8
SectionCollection GlobalSections
Gets the global sections.
Definition: Solution.cs:100