4 using System.Collections.Generic;
5 using System.Diagnostics;
6 using System.Globalization;
10 using System.Threading;
11 using System.Xml.Linq;
12 using Microsoft.Build.Evaluation;
13 using Microsoft.Build.Logging;
14 using Microsoft.Win32;
17 namespace SiliconStudio.Assets
22 internal class NugetStore
24 private const string DefaultTargets =
@"Targets\SiliconStudio.Common.targets";
26 public const string DefaultGamePackagesDirectory =
"GamePackages";
28 public const string DefaultConfig =
"store.config";
33 private const string DefaultBinDirectory =
"Bin";
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;
45 public NugetStore(
string rootDirectory,
string configFile = DefaultConfig)
47 if (rootDirectory == null)
throw new ArgumentNullException(
"rootDirectory");
48 if (configFile == null)
throw new ArgumentNullException(
"configFile");
50 var configFilePath = Path.Combine(rootDirectory, configFile);
51 if (!
File.Exists(configFilePath))
53 throw new ArgumentException(
String.Format(
"Invalid installation. Configuration file [{0}] not found", configFile),
"configFile");
57 Environment.SetEnvironmentVariable(
"NuGetCachePath", Path.Combine(rootDirectory,
"Cache"));
59 rootFileSystem =
new PhysicalFileSystem(rootDirectory);
60 settings = NuGet.Settings.LoadDefaultSettings(rootFileSystem, configFile, null);
62 string installPath = settings.GetRepositoryPath();
63 packagesFileSystem =
new PhysicalFileSystem(installPath);
64 packageSourceProvider =
new PackageSourceProvider(settings);
66 repositoryFactory =
new PackageRepositoryFactory();
67 aggregateRepository = packageSourceProvider.CreateAggregateRepository(repositoryFactory,
true);
69 pathResolver =
new DefaultPackagePathResolver(packagesFileSystem);
71 manager =
new PackageManager(aggregateRepository, pathResolver, packagesFileSystem);
74 public string RootDirectory
78 return rootFileSystem.Root;
82 public string DefaultPackageId {
get; set; }
88 return logger ?? NullLogger.Instance;
94 Manager.Logger = logger;
95 SourceRepository.Logger = logger;
99 public ISettings Settings
107 public IPackagePathResolver PathResolver
115 public PackageManager Manager
123 public IPackageRepository LocalRepository
127 return Manager.LocalRepository;
131 public AggregateRepository SourceRepository
135 return aggregateRepository;
139 public bool CheckSource()
141 return SourceRepository.Repositories.Any(CheckSource);
144 public void InstallPackage(
string packageId, SemanticVersion version)
146 using (GetLocalRepositoryLocker())
148 Manager.InstallPackage(packageId, version,
false,
true);
151 UpdateTargetsInternal();
154 InstallVsix(GetLatestPackageInstalled(packageId));
158 public void UpdatePackage(IPackage package)
160 using (GetLocalRepositoryLocker())
162 Manager.UpdatePackage(package,
true,
true);
165 UpdateTargetsInternal();
168 InstallVsix(GetLatestPackageInstalled(package.Id));
172 public void UpdateTargets()
174 using (GetLocalRepositoryLocker())
176 UpdateTargetsInternal();
180 public IPackage GetLatestPackageInstalled(
string packageId)
182 return LocalRepository.GetPackages().Where(p => p.Id == packageId).OrderByDescending(p => p.Version).FirstOrDefault();
185 public static bool CheckSource(IPackageRepository repository)
189 repository.GetPackages().FirstOrDefault();
194 if (!IsSourceUnavailableException(ex))
202 public static string GetPackageVersionVariable(
string packageId)
204 if (packageId == null)
throw new ArgumentNullException(
"packageId");
205 var newPackageId = packageId.Replace(
".", String.Empty);
206 return "SiliconStudioPackage" + newPackageId +
"Version";
209 private GlobalMutexLocker GetLocalRepositoryLocker()
211 return new GlobalMutexLocker(
"LauncherApp-" + RootDirectory);
214 private List<IPackage> UpdateTargetsInternal()
216 var projectCollection =
new ProjectCollection();
217 var project =
new Project(projectCollection);
219 var commonPropertyGroup = project.Xml.AddPropertyGroup();
220 var target = project.Xml.AddTarget(
"CheckPackages");
221 project.Xml.InitialTargets =
"CheckPackages";
223 var packages = GetRootPackagesInDependencyOrder();
224 foreach (var package
in packages)
226 var packageVar = GetPackageVersionVariable(package.Id);
227 var packageTarget = String.Format(
@"$(MSBuildThisFileDirectory)..\{0}\{1}.{2}\Targets\{1}.targets", DefaultGamePackagesDirectory, package.Id,
"$(" + packageVar +
")");
231 var importElement = project.Xml.AddImport(packageTarget);
232 importElement.Condition = String.Format(
@"Exists('{0}')", packageTarget);
235 var packageVarSaved = packageVar +
"Saved";
236 var packageVarInvalid = packageVar +
"Invalid";
239 commonPropertyGroup.AddProperty(packageVarSaved,
"$(" + packageVar +
")");
242 commonPropertyGroup.AddProperty(packageVarInvalid,
"true").Condition =
"'$(" + packageVar +
")' == '' or !" + importElement.Condition;
245 var invalidProperty = commonPropertyGroup.AddProperty(packageVar, package.Version.ToString());
246 invalidProperty.Condition =
"'$(" + packageVarInvalid +
")' == 'true'";
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));
257 var targetFile = Path.Combine(RootDirectory, DefaultTargets);
258 if (
File.Exists(targetFile))
260 File.Delete(targetFile);
262 project.Save(targetFile);
267 private List<IPackage> GetRootPackagesInDependencyOrder()
269 var packagesInOrder =
new List<IPackage>();
272 var packages =
new HashSet<IPackage>();
273 foreach (var package
in LocalRepository.GetPackages().OrderBy(p => p.Id).ThenByDescending(p => p.Version))
275 if (packages.All(p => p.Id != package.Id))
277 packages.Add(package);
281 while (packages.Count > 0)
283 var nextPackage = packages.FirstOrDefault();
284 AddPackageRecursive(packagesInOrder, packages, nextPackage);
287 return packagesInOrder;
290 private void AddPackageRecursive(List<IPackage> packagesOut, HashSet<IPackage> packages, IPackage packageToTrack)
293 var dependencies = packageToTrack.DependencySets.SelectMany(deps => deps.Dependencies);
294 foreach (var dependency
in dependencies)
296 var nextPackage = packages.FirstOrDefault(p => p.Id == dependency.Id);
297 if (nextPackage != null)
299 AddPackageRecursive(packagesOut, packages, nextPackage);
304 packagesOut.Add(packageToTrack);
307 packages.Remove(packageToTrack);
310 private void InstallVsix(IPackage package)
317 var packageDirectory = PathResolver.GetInstallPath(package);
318 InstallVsixFromPackageDirectory(packageDirectory);
321 internal void InstallVsixFromPackageDirectory(
string packageDirectory)
323 var vsixInstallerPath = FindLatestVsixInstaller();
324 if (vsixInstallerPath == null)
329 var files = Directory.EnumerateFiles(Path.Combine(packageDirectory, DefaultBinDirectory),
"*.vsix", SearchOption.AllDirectories);
330 foreach (var file
in files)
332 InstallVsix(vsixInstallerPath, file);
336 private void InstallVsix(
string vsixInstallerPath,
string pathToVsix)
340 var vsixId = GetVsixId(pathToVsix);
341 if (vsixId == Guid.Empty)
343 throw new InvalidOperationException(
string.Format(
"Invalid VSIX package [{0}]", pathToVsix));
346 var vsixName = Path.GetFileNameWithoutExtension(pathToVsix);
349 Logger.Log(MessageLevel.Info,
"Installing Visual Studio Package [{0}]", vsixName);
351 RunVsixInstaller(vsixInstallerPath,
"/q /uninstall:" + vsixId.ToString(
"D", CultureInfo.InvariantCulture));
354 RunVsixInstaller(vsixInstallerPath,
"/q \"" + pathToVsix +
"\"");
357 private static bool RunVsixInstaller(
string pathToVsixInstaller,
string arguments)
361 var process = Process.Start(pathToVsixInstaller, arguments);
366 process.WaitForExit();
367 return process.ExitCode == 0;
376 private static Guid GetVsixId(
string pathToVsix)
378 if (pathToVsix == null)
throw new ArgumentNullException(
"pathToVsix");
381 using (var stream = File.OpenRead(pathToVsix))
383 var
package = System.IO.Packaging.Package.Open(stream);
385 var uri = System.IO.Packaging.PackUriHelper.CreatePartUri(
new Uri(
"extension.vsixmanifest", UriKind.Relative));
386 var manifest = package.GetPart(uri);
388 var doc = XElement.Load(manifest.GetStream());
389 var identity = doc.Descendants().FirstOrDefault(element => element.Name.LocalName ==
"Identity");
390 if (identity != null)
392 var idAttribute = identity.Attribute(
"Id");
393 if (idAttribute != null)
395 Guid.TryParse(idAttribute.Value, out id);
403 private static string FindLatestVsixInstaller()
405 var key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
406 var subKey = key.OpenSubKey(
@"SOFTWARE\Microsoft\VisualStudio\");
412 var versions =
new Dictionary<Version, string>();
413 foreach (var subKeyName
in subKey.GetSubKeyNames())
416 if (Version.TryParse(subKeyName, out version))
418 versions.Add(version, subKeyName);
422 foreach (var version
in versions.Keys.OrderByDescending(v => v))
424 var subKeyName = versions[version];
426 var vsKey = subKey.OpenSubKey(subKeyName);
428 var installDirValue = vsKey.GetValue(
"InstallDir");
429 if (installDirValue != null)
431 var installDir = installDirValue.ToString();
432 var vsixInstallerPath = Path.Combine(installDir,
"VSIXInstaller.exe");
433 if (
File.Exists(vsixInstallerPath))
435 return vsixInstallerPath;
444 public static bool IsSourceUnavailableException(
Exception ex)
446 return (((ex is WebException) ||
447 (ex.InnerException is WebException) ||
448 (ex.InnerException is InvalidOperationException)));
451 private class GlobalMutexLocker : IDisposable
454 private readonly
bool owned;
456 public GlobalMutexLocker(
string name)
458 name = name.Replace(
":",
"_");
459 name = name.Replace(
"/",
"_");
460 name = name.Replace(
"\\",
"_");
461 mutex =
new Mutex(
true, name, out owned);
464 owned = mutex.WaitOne();
468 public void Dispose()
472 mutex.ReleaseMutex();
478 public static bool IsStoreDirectory(
string directory)
480 if (directory == null)
throw new ArgumentNullException(
"directory");
481 var storeConfig = Path.Combine(directory, DefaultConfig);
482 return File.Exists(storeConfig) && HasPackages(directory);
485 private static bool HasPackages(
string directory)
487 if (directory == null)
throw new ArgumentNullException(
"directory");
488 var commonTargets = Path.Combine(directory, DefaultGamePackagesDirectory);
489 return Directory.Exists(commonTargets);