Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
NugetStore.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.Diagnostics;
6 using System.Globalization;
7 using System.IO;
8 using System.Linq;
9 using System.Net;
10 using System.Threading;
11 using System.Xml.Linq;
12 using Microsoft.Build.Evaluation;
13 using Microsoft.Build.Logging;
14 using Microsoft.Win32;
15 using NuGet;
16 
17 namespace SiliconStudio.Assets
18 {
19  /// <summary>
20  /// Internal class to store nuget objects
21  /// </summary>
22  internal class NugetStore
23  {
24  private const string DefaultTargets = @"Targets\SiliconStudio.Common.targets";
25 
26  public const string DefaultGamePackagesDirectory = "GamePackages";
27 
28  public const string DefaultConfig = "store.config";
29 
30  /// <summary>
31  /// Used to lookup for Vsix
32  /// </summary>
33  private const string DefaultBinDirectory = "Bin";
34 
35  private readonly PhysicalFileSystem rootFileSystem;
36  private readonly ISettings settings;
37  private readonly IFileSystem packagesFileSystem;
38  private readonly PackageSourceProvider packageSourceProvider;
39  private readonly DefaultPackagePathResolver pathResolver;
40  private readonly PackageRepositoryFactory repositoryFactory;
41  private readonly AggregateRepository aggregateRepository;
42  private readonly PackageManager manager;
43  private ILogger logger;
44 
45  public NugetStore(string rootDirectory, string configFile = DefaultConfig)
46  {
47  if (rootDirectory == null) throw new ArgumentNullException("rootDirectory");
48  if (configFile == null) throw new ArgumentNullException("configFile");
49 
50  var configFilePath = Path.Combine(rootDirectory, configFile);
51  if (!File.Exists(configFilePath))
52  {
53  throw new ArgumentException(String.Format("Invalid installation. Configuration file [{0}] not found", configFile), "configFile");
54  }
55 
56  // Setup NugetCachePath in the cache folder
57  Environment.SetEnvironmentVariable("NuGetCachePath", Path.Combine(rootDirectory, "Cache"));
58 
59  rootFileSystem = new PhysicalFileSystem(rootDirectory);
60  settings = NuGet.Settings.LoadDefaultSettings(rootFileSystem, configFile, null);
61 
62  string installPath = settings.GetRepositoryPath();
63  packagesFileSystem = new PhysicalFileSystem(installPath);
64  packageSourceProvider = new PackageSourceProvider(settings);
65 
66  repositoryFactory = new PackageRepositoryFactory();
67  aggregateRepository = packageSourceProvider.CreateAggregateRepository(repositoryFactory, true);
68 
69  pathResolver = new DefaultPackagePathResolver(packagesFileSystem);
70 
71  manager = new PackageManager(aggregateRepository, pathResolver, packagesFileSystem);
72  }
73 
74  public string RootDirectory
75  {
76  get
77  {
78  return rootFileSystem.Root;
79  }
80  }
81 
82  public string DefaultPackageId { get; set; }
83 
84  public ILogger Logger
85  {
86  get
87  {
88  return logger ?? NullLogger.Instance;
89  }
90 
91  set
92  {
93  logger = value;
94  Manager.Logger = logger;
95  SourceRepository.Logger = logger;
96  }
97  }
98 
99  public ISettings Settings
100  {
101  get
102  {
103  return settings;
104  }
105  }
106 
107  public IPackagePathResolver PathResolver
108  {
109  get
110  {
111  return pathResolver;
112  }
113  }
114 
115  public PackageManager Manager
116  {
117  get
118  {
119  return manager;
120  }
121  }
122 
123  public IPackageRepository LocalRepository
124  {
125  get
126  {
127  return Manager.LocalRepository;
128  }
129  }
130 
131  public AggregateRepository SourceRepository
132  {
133  get
134  {
135  return aggregateRepository;
136  }
137  }
138 
139  public bool CheckSource()
140  {
141  return SourceRepository.Repositories.Any(CheckSource);
142  }
143 
144  public void InstallPackage(string packageId, SemanticVersion version)
145  {
146  using (GetLocalRepositoryLocker())
147  {
148  Manager.InstallPackage(packageId, version, false, true);
149 
150  // Every time a new package is installed, we are updating the common targets
151  UpdateTargetsInternal();
152 
153  // Install vsix
154  InstallVsix(GetLatestPackageInstalled(packageId));
155  }
156  }
157 
158  public void UpdatePackage(IPackage package)
159  {
160  using (GetLocalRepositoryLocker())
161  {
162  Manager.UpdatePackage(package, true, true);
163 
164  // Every time a new package is installed, we are updating the common targets
165  UpdateTargetsInternal();
166 
167  // Install vsix
168  InstallVsix(GetLatestPackageInstalled(package.Id));
169  }
170  }
171 
172  public void UpdateTargets()
173  {
174  using (GetLocalRepositoryLocker())
175  {
176  UpdateTargetsInternal();
177  }
178  }
179 
180  public IPackage GetLatestPackageInstalled(string packageId)
181  {
182  return LocalRepository.GetPackages().Where(p => p.Id == packageId).OrderByDescending(p => p.Version).FirstOrDefault();
183  }
184 
185  public static bool CheckSource(IPackageRepository repository)
186  {
187  try
188  {
189  repository.GetPackages().FirstOrDefault();
190  return true;
191  }
192  catch (Exception ex)
193  {
194  if (!IsSourceUnavailableException(ex))
195  {
196  throw;
197  }
198  }
199  return false;
200  }
201 
202  public static string GetPackageVersionVariable(string packageId)
203  {
204  if (packageId == null) throw new ArgumentNullException("packageId");
205  var newPackageId = packageId.Replace(".", String.Empty);
206  return "SiliconStudioPackage" + newPackageId + "Version";
207  }
208 
209  private GlobalMutexLocker GetLocalRepositoryLocker()
210  {
211  return new GlobalMutexLocker("LauncherApp-" + RootDirectory);
212  }
213 
214  private List<IPackage> UpdateTargetsInternal()
215  {
216  var projectCollection = new ProjectCollection();
217  var project = new Project(projectCollection);
218 
219  var commonPropertyGroup = project.Xml.AddPropertyGroup();
220  var target = project.Xml.AddTarget("CheckPackages");
221  project.Xml.InitialTargets = "CheckPackages";
222 
223  var packages = GetRootPackagesInDependencyOrder();
224  foreach (var package in packages)
225  {
226  var packageVar = GetPackageVersionVariable(package.Id);
227  var packageTarget = String.Format(@"$(MSBuildThisFileDirectory)..\{0}\{1}.{2}\Targets\{1}.targets", DefaultGamePackagesDirectory, package.Id, "$(" + packageVar + ")");
228 
229  // Add import
230  // <Import Project="..\Packages\Paradox$(SiliconStudioPackageParadoxVersion)\Targets\Paradox.targets" Condition="Exists('..\Packages\Paradox.$(SiliconStudioPackageParadoxVersion)\Targets\Paradox.targets')" />
231  var importElement = project.Xml.AddImport(packageTarget);
232  importElement.Condition = String.Format(@"Exists('{0}')", packageTarget);
233 
234  // Add common properties
235  var packageVarSaved = packageVar + "Saved";
236  var packageVarInvalid = packageVar + "Invalid";
237 
238  // <SiliconStudioPackageParadoxVersionSaved>$(SiliconStudioPackageParadoxVersion)</SiliconStudioPackageParadoxVersionSaved>
239  commonPropertyGroup.AddProperty(packageVarSaved, "$(" + packageVar + ")");
240 
241  // <SiliconStudioPackageParadoxVersionInvalid Condition="'$(SiliconStudioPackageParadoxVersion)' == '' or !Exists('..\Packages\Paradox.$(SiliconStudioPackageParadoxVersion)\Targets\Paradox.targets')">true</SiliconStudioPackageParadoxVersionInvalid>
242  commonPropertyGroup.AddProperty(packageVarInvalid, "true").Condition = "'$(" + packageVar + ")' == '' or !" + importElement.Condition;
243 
244  // <SiliconStudioPackageParadoxVersion Condition="'$(SiliconStudioPackageParadoxVersionInvalid)' == 'true'">1.0.0-alpha01</SiliconStudioPackageParadoxVersion>
245  var invalidProperty = commonPropertyGroup.AddProperty(packageVar, package.Version.ToString());
246  invalidProperty.Condition = "'$(" + packageVarInvalid + ")' == 'true'";
247 
248  // Add in CheckPackages target
249  // <Warning Condition="$(SiliconStudioPackageParadoxVersionInvalid) == 'true'" Text="Package Paradox $(SiliconStudioPackageParadoxVersionSaved) not found. Use version $(SiliconStudioPackageParadoxVersion) instead"/>
250  // Disable Warning and use only Message for now
251  // TODO: Provide a better diagnostic message (in case the version is really not found or rerouted to a newer version)
252  var warningTask = target.AddTask("Message");
253  warningTask.Condition = invalidProperty.Condition;
254  warningTask.SetParameter("Text", String.Format("Package {0} with version [$({1})] not found. Use version $({2}) instead", package.Id, packageVarSaved, packageVar));
255  }
256 
257  var targetFile = Path.Combine(RootDirectory, DefaultTargets);
258  if (File.Exists(targetFile))
259  {
260  File.Delete(targetFile);
261  }
262  project.Save(targetFile);
263 
264  return packages;
265  }
266 
267  private List<IPackage> GetRootPackagesInDependencyOrder()
268  {
269  var packagesInOrder = new List<IPackage>();
270 
271  // Get all packages
272  var packages = new HashSet<IPackage>();
273  foreach (var package in LocalRepository.GetPackages().OrderBy(p => p.Id).ThenByDescending(p => p.Version))
274  {
275  if (packages.All(p => p.Id != package.Id))
276  {
277  packages.Add(package);
278  }
279  }
280 
281  while (packages.Count > 0)
282  {
283  var nextPackage = packages.FirstOrDefault();
284  AddPackageRecursive(packagesInOrder, packages, nextPackage);
285  }
286 
287  return packagesInOrder;
288  }
289 
290  private void AddPackageRecursive(List<IPackage> packagesOut, HashSet<IPackage> packages, IPackage packageToTrack)
291  {
292  // Go first recursively with all dependencies resolved
293  var dependencies = packageToTrack.DependencySets.SelectMany(deps => deps.Dependencies);
294  foreach (var dependency in dependencies)
295  {
296  var nextPackage = packages.FirstOrDefault(p => p.Id == dependency.Id);
297  if (nextPackage != null)
298  {
299  AddPackageRecursive(packagesOut, packages, nextPackage);
300  }
301  }
302 
303  // This package is now resolved, add it to the ordered list
304  packagesOut.Add(packageToTrack);
305 
306  // Remove it from the list of packages to process
307  packages.Remove(packageToTrack);
308  }
309 
310  private void InstallVsix(IPackage package)
311  {
312  if (package == null)
313  {
314  return;
315  }
316 
317  var packageDirectory = PathResolver.GetInstallPath(package);
318  InstallVsixFromPackageDirectory(packageDirectory);
319  }
320 
321  internal void InstallVsixFromPackageDirectory(string packageDirectory)
322  {
323  var vsixInstallerPath = FindLatestVsixInstaller();
324  if (vsixInstallerPath == null)
325  {
326  return;
327  }
328 
329  var files = Directory.EnumerateFiles(Path.Combine(packageDirectory, DefaultBinDirectory), "*.vsix", SearchOption.AllDirectories);
330  foreach (var file in files)
331  {
332  InstallVsix(vsixInstallerPath, file);
333  }
334  }
335 
336  private void InstallVsix(string vsixInstallerPath, string pathToVsix)
337  {
338  // Uninstall previous vsix
339 
340  var vsixId = GetVsixId(pathToVsix);
341  if (vsixId == Guid.Empty)
342  {
343  throw new InvalidOperationException(string.Format("Invalid VSIX package [{0}]", pathToVsix));
344  }
345 
346  var vsixName = Path.GetFileNameWithoutExtension(pathToVsix);
347 
348  // Log just one message when installing the visual studio package
349  Logger.Log(MessageLevel.Info, "Installing Visual Studio Package [{0}]", vsixName);
350 
351  RunVsixInstaller(vsixInstallerPath, "/q /uninstall:" + vsixId.ToString("D", CultureInfo.InvariantCulture));
352 
353  // Install new vsix
354  RunVsixInstaller(vsixInstallerPath, "/q \"" + pathToVsix + "\"");
355  }
356 
357  private static bool RunVsixInstaller(string pathToVsixInstaller, string arguments)
358  {
359  try
360  {
361  var process = Process.Start(pathToVsixInstaller, arguments);
362  if (process == null)
363  {
364  return false;
365  }
366  process.WaitForExit();
367  return process.ExitCode == 0;
368  }
369  catch (Exception)
370  {
371  }
372 
373  return false;
374  }
375 
376  private static Guid GetVsixId(string pathToVsix)
377  {
378  if (pathToVsix == null) throw new ArgumentNullException("pathToVsix");
379 
380  var id = Guid.Empty;
381  using (var stream = File.OpenRead(pathToVsix))
382  {
383  var package = System.IO.Packaging.Package.Open(stream);
384 
385  var uri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("extension.vsixmanifest", UriKind.Relative));
386  var manifest = package.GetPart(uri);
387 
388  var doc = XElement.Load(manifest.GetStream());
389  var identity = doc.Descendants().FirstOrDefault(element => element.Name.LocalName == "Identity");
390  if (identity != null)
391  {
392  var idAttribute = identity.Attribute("Id");
393  if (idAttribute != null)
394  {
395  Guid.TryParse(idAttribute.Value, out id);
396  }
397  }
398  }
399 
400  return id;
401  }
402 
403  private static string FindLatestVsixInstaller()
404  {
405  var key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
406  var subKey = key.OpenSubKey(@"SOFTWARE\Microsoft\VisualStudio\");
407  if (subKey == null)
408  {
409  return null;
410  }
411 
412  var versions = new Dictionary<Version, string>();
413  foreach (var subKeyName in subKey.GetSubKeyNames())
414  {
415  Version version;
416  if (Version.TryParse(subKeyName, out version))
417  {
418  versions.Add(version, subKeyName);
419  }
420  }
421 
422  foreach (var version in versions.Keys.OrderByDescending(v => v))
423  {
424  var subKeyName = versions[version];
425 
426  var vsKey = subKey.OpenSubKey(subKeyName);
427 
428  var installDirValue = vsKey.GetValue("InstallDir");
429  if (installDirValue != null)
430  {
431  var installDir = installDirValue.ToString();
432  var vsixInstallerPath = Path.Combine(installDir, "VSIXInstaller.exe");
433  if (File.Exists(vsixInstallerPath))
434  {
435  return vsixInstallerPath;
436  }
437  }
438  }
439 
440  return null;
441  }
442 
443 
444  public static bool IsSourceUnavailableException(Exception ex)
445  {
446  return (((ex is WebException) ||
447  (ex.InnerException is WebException) ||
448  (ex.InnerException is InvalidOperationException)));
449  }
450 
451  private class GlobalMutexLocker : IDisposable
452  {
453  private Mutex mutex;
454  private readonly bool owned;
455 
456  public GlobalMutexLocker(string name)
457  {
458  name = name.Replace(":", "_");
459  name = name.Replace("/", "_");
460  name = name.Replace("\\", "_");
461  mutex = new Mutex(true, name, out owned);
462  if (!owned)
463  {
464  owned = mutex.WaitOne();
465  }
466  }
467 
468  public void Dispose()
469  {
470  if (owned)
471  {
472  mutex.ReleaseMutex();
473  }
474  mutex = null;
475  }
476  }
477 
478  public static bool IsStoreDirectory(string directory)
479  {
480  if (directory == null) throw new ArgumentNullException("directory");
481  var storeConfig = Path.Combine(directory, DefaultConfig);
482  return File.Exists(storeConfig) && HasPackages(directory);
483  }
484 
485  private static bool HasPackages(string directory)
486  {
487  if (directory == null) throw new ArgumentNullException("directory");
488  var commonTargets = Path.Combine(directory, DefaultGamePackagesDirectory);
489  return Directory.Exists(commonTargets);
490  }
491  }
492 }
System.IO.File File
EnvDTE.Project Project
Android.Net.Uri Uri
Definition: HtmlElement.cs:8