diff --git a/src/Cli/dotnet/NugetPackageInstaller/NuGetPackageInstaller.cs b/src/Cli/dotnet/NugetPackageInstaller/NuGetPackageInstaller.cs index 74f1819dfff9..9b7aff5d4f89 100644 --- a/src/Cli/dotnet/NugetPackageInstaller/NuGetPackageInstaller.cs +++ b/src/Cli/dotnet/NugetPackageInstaller/NuGetPackageInstaller.cs @@ -218,7 +218,7 @@ await Task.WhenAll( if (!accumulativeSearchResults.Any()) { - _logger.LogWarning( + throw new Exception( string.Format( LocalizableStrings.FailedToLoadNuGetSourceSourceIsNotValid, packageIdentifier, diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/IWorkloadManifestUpdater.cs b/src/Cli/dotnet/commands/dotnet-workload/install/IWorkloadManifestUpdater.cs new file mode 100644 index 000000000000..044c6c1f6f31 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-workload/install/IWorkloadManifestUpdater.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.DotNet.Workloads.Workload.Install.InstallRecord; + +namespace Microsoft.DotNet.Workloads.Workload.Install +{ + internal interface IWorkloadManifestUpdater + { + Task UpdateAdvertisingManifestsAsync(SdkFeatureBand featureBand, bool includePreviews); + + IEnumerable<(ManifestId manifestId, ManifestVersion existingVersion, ManifestVersion newVersion)> CalculateManifestUpdates(SdkFeatureBand featureBand); + } +} diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/LocalizableStrings.resx b/src/Cli/dotnet/commands/dotnet-workload/install/LocalizableStrings.resx index b9df77d85b2d..da357cf71a1f 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/LocalizableStrings.resx +++ b/src/Cli/dotnet/commands/dotnet-workload/install/LocalizableStrings.resx @@ -210,4 +210,22 @@ Rolling back pack {0} installation... + + Updated advertising manifest {0}. + + + Failed to update the advertising manifest {0}: {1} + + + No manifest with id {0} exists. + + + Failed to install manifest {0} version {1}: {2}. + + + Installing workload manifest {0} version {1}. + + + Allow prerelease workload manifests. + diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/ManifestVersion.cs b/src/Cli/dotnet/commands/dotnet-workload/install/ManifestVersion.cs index 9f0e375c153f..2ef4e1c15e9e 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/ManifestVersion.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/ManifestVersion.cs @@ -2,26 +2,30 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using Microsoft.DotNet.MSBuildSdkResolver; namespace Microsoft.DotNet.Workloads.Workload.Install { - internal struct ManifestVersion : IEquatable, IComparable + internal class ManifestVersion : IEquatable, IComparable { - private string _version; + private FXVersion _version; public ManifestVersion(string version) { - _version = version ?? throw new ArgumentNullException(nameof(version)); + if (!FXVersion.TryParse(version, out _version)) + { + throw new ArgumentNullException(nameof(version)); + } } public bool Equals(ManifestVersion other) { - return ToString() == other.ToString(); + return ToString().Equals(other.ToString()); } public int CompareTo(ManifestVersion other) { - return string.Compare(ToString(), other.ToString(), StringComparison.Ordinal); + return FXVersion.Compare(_version, other._version); } public override bool Equals(object obj) @@ -31,12 +35,12 @@ public override bool Equals(object obj) public override int GetHashCode() { - return ToString().GetHashCode(); + return _version.GetHashCode(); } public override string ToString() { - return _version; + return _version.ToString(); } } } diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/NetSdkManagedInstaller.cs b/src/Cli/dotnet/commands/dotnet-workload/install/NetSdkManagedInstaller.cs index 3b3a3c5f3d7b..877aa9173cbf 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/NetSdkManagedInstaller.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/NetSdkManagedInstaller.cs @@ -11,11 +11,11 @@ using Microsoft.DotNet.ToolPackage; using Microsoft.Extensions.EnvironmentAbstractions; using Microsoft.NET.Sdk.WorkloadManifestReader; +using NuGet.Common; using NuGet.Versioning; using static Microsoft.NET.Sdk.WorkloadManifestReader.WorkloadResolver; using EnvironmentProvider = Microsoft.DotNet.NativeWrapper.EnvironmentProvider; using Microsoft.DotNet.Workloads.Workload.Install.InstallRecord; -using NuGet.Common; namespace Microsoft.DotNet.Workloads.Workload.Install { @@ -26,7 +26,7 @@ internal class NetSdkManagedInstaller : IWorkloadPackInstaller private readonly string _installedPacksDir = "InstalledPacks"; protected readonly string _dotnetDir; protected readonly DirectoryPath _tempPackagesDir; - private readonly INuGetPackageDownloader _nugetPackageInstaller; + private readonly INuGetPackageDownloader _nugetPackageDownloader; private readonly IWorkloadResolver _workloadResolver; private readonly SdkFeatureBand _sdkFeatureBand; private readonly NetSdkManagedInstallationRecordRepository _installationRecordRepository; @@ -41,7 +41,7 @@ public NetSdkManagedInstaller( { _dotnetDir = dotnetDir ?? Path.GetDirectoryName(Environment.ProcessPath); _tempPackagesDir = new DirectoryPath(Path.Combine(_dotnetDir, "metadata", "temp")); - _nugetPackageInstaller = nugetPackageDownloader ?? + _nugetPackageDownloader = nugetPackageDownloader ?? new NuGetPackageDownloader(_tempPackagesDir, verbosity.VerbosityIsDetailedOrDiagnostic() ? new NuGetConsoleLogger() : new NullLogger()); _workloadMetadataDir = Path.Combine(_dotnetDir, "metadata", "workloads"); _reporter = reporter; @@ -87,7 +87,7 @@ public void InstallWorkloadPack(PackInfo packInfo, SdkFeatureBand sdkFeatureBand { if (!PackIsInstalled(packInfo)) { - var packagePath = _nugetPackageInstaller.DownloadPackageAsync(new PackageId(packInfo.ResolvedPackageId), new NuGetVersion(packInfo.Version)).Result; + var packagePath = _nugetPackageDownloader.DownloadPackageAsync(new PackageId(packInfo.ResolvedPackageId), new NuGetVersion(packInfo.Version)).Result; tempFilesToDelete.Add(packagePath); if (!Directory.Exists(Path.GetDirectoryName(packInfo.Path))) @@ -104,7 +104,7 @@ public void InstallWorkloadPack(PackInfo packInfo, SdkFeatureBand sdkFeatureBand var tempExtractionDir = Path.Combine(_tempPackagesDir.Value, $"{packInfo.Id}-{packInfo.Version}-extracted"); tempDirsToDelete.Add(tempExtractionDir); Directory.CreateDirectory(tempExtractionDir); - var packFiles = _nugetPackageInstaller.ExtractPackageAsync(packagePath, tempExtractionDir).Result; + var packFiles = _nugetPackageDownloader.ExtractPackageAsync(packagePath, tempExtractionDir).Result; FileAccessRetrier.RetryOnMoveAccessFailure(() => Directory.Move(tempExtractionDir, packInfo.Path)); } @@ -158,7 +158,77 @@ public void RollBackWorkloadPackInstall(PackInfo packInfo, SdkFeatureBand sdkFea } } - public void InstallWorkloadManifest(ManifestId manifestId, ManifestVersion manifestVersion, SdkFeatureBand sdkFeatureBand) => throw new NotImplementedException(); + public void InstallWorkloadManifest(ManifestId manifestId, ManifestVersion manifestVersion, SdkFeatureBand sdkFeatureBand) + { + string packagePath = null; + string tempExtractionDir = null; + string tempBackupDir = null; + var manifestPath = Path.Combine(_dotnetDir, "sdk-manifests", sdkFeatureBand.ToString(), manifestId.ToString()); + + _reporter.WriteLine(string.Format(LocalizableStrings.InstallingWorkloadManifest, manifestId, manifestVersion)); + + try + { + TransactionalAction.Run( + action: () => + { + packagePath = _nugetPackageDownloader.DownloadPackageAsync(WorkloadManifestUpdater.GetManifestPackageId(sdkFeatureBand, manifestId), new NuGetVersion(manifestVersion.ToString())).Result; + tempExtractionDir = Path.Combine(_tempPackagesDir.Value, $"{manifestId}-{manifestVersion}-extracted"); + Directory.CreateDirectory(tempExtractionDir); + var manifestFiles = _nugetPackageDownloader.ExtractPackageAsync(packagePath, tempExtractionDir).Result; + + if (Directory.Exists(manifestPath) && Directory.GetFileSystemEntries(manifestPath).Any()) + { + // Backup existing manifest data for roll back purposes + tempBackupDir = Path.Combine(_tempPackagesDir.Value, $"{manifestId}-{manifestVersion}-backup"); + if (Directory.Exists(tempBackupDir)) + { + Directory.Delete(tempBackupDir, true); + } + FileAccessRetrier.RetryOnMoveAccessFailure(() => Directory.Move(manifestPath, tempBackupDir)); + } + Directory.CreateDirectory(Path.GetDirectoryName(manifestPath)); + FileAccessRetrier.RetryOnMoveAccessFailure(() => Directory.Move(Path.Combine(tempExtractionDir, "data"), manifestPath)); + }, + rollback: () => { + if (!string.IsNullOrEmpty(tempBackupDir) && Directory.Exists(tempBackupDir)) + { + FileAccessRetrier.RetryOnMoveAccessFailure(() => Directory.Move(tempBackupDir, manifestPath)); + } + }); + + // Delete leftover dirs and files + if (!string.IsNullOrEmpty(packagePath) && File.Exists(packagePath)) + { + File.Delete(packagePath); + } + + var versionDir = Path.GetDirectoryName(packagePath); + if (Directory.Exists(versionDir) && !Directory.GetFileSystemEntries(versionDir).Any()) + { + Directory.Delete(versionDir); + var idDir = Path.GetDirectoryName(versionDir); + if (Directory.Exists(idDir) && !Directory.GetFileSystemEntries(idDir).Any()) + { + Directory.Delete(idDir); + } + } + + if (!string.IsNullOrEmpty(tempExtractionDir) && Directory.Exists(tempExtractionDir)) + { + Directory.Delete(tempExtractionDir, true); + } + + if (!string.IsNullOrEmpty(tempBackupDir) && Directory.Exists(tempBackupDir)) + { + Directory.Delete(tempBackupDir, true); + } + } + catch (Exception e) + { + throw new Exception(string.Format(LocalizableStrings.FailedToInstallWorkloadManifest, manifestId, manifestVersion, e.Message)); + } + } public void DownloadToOfflineCache(IEnumerable manifests) => throw new NotImplementedException(); @@ -208,7 +278,7 @@ private IEnumerable GetExpectedPackInstallRecords(SdkFeatureBand sdkFeat { var installedWorkloads = _installationRecordRepository.GetInstalledWorkloads(sdkFeatureBand); return installedWorkloads - .SelectMany(workload => _workloadResolver.GetPacksInWorkload(workload)) + .SelectMany(workload => _workloadResolver.GetPacksInWorkload(workload.ToString())) .Select(pack => _workloadResolver.TryGetPackInfo(pack)) .Select(packInfo => GetPackInstallRecordPath(packInfo, sdkFeatureBand)); } diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallCommand.cs b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallCommand.cs index 444a064fce82..950425894b29 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallCommand.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallCommand.cs @@ -17,7 +17,11 @@ using NuGet.Protocol; using NuGet.Protocol.Core.Types; using NuGet.Versioning; +using Microsoft.DotNet.Cli.NuGetPackageDownloader; +using Microsoft.Extensions.EnvironmentAbstractions; +using NuGet.Common; using Microsoft.DotNet.Workloads.Workload.Install.InstallRecord; +using static Microsoft.NET.Sdk.WorkloadManifestReader.WorkloadResolver; namespace Microsoft.DotNet.Workloads.Workload.Install { @@ -27,10 +31,14 @@ internal class WorkloadInstallCommand : CommandBase private readonly bool _skipManifestUpdate; private readonly string _fromCacheOption; private readonly bool _printDownloadLinkOnly; + private readonly bool _includePreviews; private readonly VerbosityOptions _verbosity; private readonly IReadOnlyCollection _workloadIds; private readonly IInstaller _workloadInstaller; private readonly IWorkloadResolver _workloadResolver; + private readonly IWorkloadManifestProvider _workloadManifestProvider; + private readonly INuGetPackageDownloader _nugetPackageDownloader; + private readonly IWorkloadManifestUpdater _workloadManifestUpdater; private readonly ReleaseVersion _sdkVersion; public readonly string MockInstallDirectory = Path.Combine(CliFolderPathCalculator.DotnetUserProfileFolderPath, @@ -41,11 +49,15 @@ public WorkloadInstallCommand( IReporter reporter = null, IWorkloadResolver workloadResolver = null, IInstaller workloadInstaller = null, + INuGetPackageDownloader nugetPackageDownloader = null, + IWorkloadManifestUpdater workloadManifestUpdater = null, + string userHome = null, string version = null) : base(parseResult) { _reporter = reporter ?? Reporter.Output; _skipManifestUpdate = parseResult.ValueForOption(WorkloadInstallCommandParser.SkipManifestUpdateOption); + _includePreviews = parseResult.ValueForOption(WorkloadInstallCommandParser.IncludePreviewOption); _printDownloadLinkOnly = parseResult.ValueForOption(WorkloadInstallCommandParser.PrintDownloadLinkOnlyOption); _fromCacheOption = parseResult.ValueForOption(WorkloadInstallCommandParser.FromCacheOption); _workloadIds = parseResult.ValueForArgument>(WorkloadInstallCommandParser.WorkloadIdArgument).ToList().AsReadOnly(); @@ -53,10 +65,14 @@ public WorkloadInstallCommand( _sdkVersion = new ReleaseVersion(version ?? Product.Version); var dotnetPath = Path.GetDirectoryName(Environment.ProcessPath); - var workloadManifestProvider = new SdkDirectoryWorkloadManifestProvider(dotnetPath, _sdkVersion.ToString()); - _workloadResolver = workloadResolver ?? WorkloadResolver.Create(workloadManifestProvider, dotnetPath, _sdkVersion.ToString()); + _workloadManifestProvider = new SdkDirectoryWorkloadManifestProvider(dotnetPath, _sdkVersion.ToString()); + _workloadResolver = workloadResolver ?? WorkloadResolver.Create(_workloadManifestProvider, dotnetPath, _sdkVersion.ToString()); var sdkFeatureBand = new SdkFeatureBand(_sdkVersion); _workloadInstaller = workloadInstaller ?? WorkloadInstallerFactory.GetWorkloadInstaller(_reporter, sdkFeatureBand, _workloadResolver, _verbosity); + userHome = userHome ?? CliFolderPathCalculator.DotnetHomePath; + var tempPackagesDir = new DirectoryPath(Path.Combine(userHome, ".dotnet", "sdk-advertising-temp")); + _nugetPackageDownloader = nugetPackageDownloader ?? new NuGetPackageDownloader(tempPackagesDir, new NullLogger()); + _workloadManifestUpdater = workloadManifestUpdater ?? new WorkloadManifestUpdater(_reporter, _workloadManifestProvider, _nugetPackageDownloader, userHome); } public override int Execute() @@ -122,9 +138,9 @@ public override int Execute() { try { - InstallWorkloads(_workloadIds.Select(id => new WorkloadId(id)), _skipManifestUpdate); + InstallWorkloads(_workloadIds.Select(id => new WorkloadId(id)), _skipManifestUpdate, _includePreviews); } - catch(Exception e) + catch (Exception e) { // Don't show entire stack trace throw new GracefulException(string.Format(LocalizableStrings.WorkloadInstallationFailed, e.Message), e); @@ -149,17 +165,23 @@ public string nupkgUrl(string baseUri, string id, NuGetVersion version) => "." + version.ToNormalizedString() + ".nupkg"; - public void InstallWorkloads(IEnumerable workloadIds, bool skipManifestUpdate = false) + public void InstallWorkloads(IEnumerable workloadIds, bool skipManifestUpdate = false, bool includePreviews = false) { _reporter.WriteLine(); var featureBand = new SdkFeatureBand(string.Join('.', _sdkVersion.Major, _sdkVersion.Minor, _sdkVersion.SdkFeatureBand)); + IEnumerable<(ManifestId, ManifestVersion, ManifestVersion)> manifestsToUpdate = new List<(ManifestId, ManifestVersion, ManifestVersion)>(); if (!skipManifestUpdate) { - throw new NotImplementedException(); + // Update currently installed workloads + var installedWorkloads = _workloadInstaller.GetWorkloadInstallationRecordRepository().GetInstalledWorkloads(featureBand); + workloadIds = workloadIds.Concat(installedWorkloads).Distinct(); + + _workloadManifestUpdater.UpdateAdvertisingManifestsAsync(featureBand, includePreviews).Wait(); + manifestsToUpdate = _workloadManifestUpdater.CalculateManifestUpdates(featureBand); } - InstallWorkloadsWithInstallRecord(workloadIds, featureBand); + InstallWorkloadsWithInstallRecord(workloadIds, featureBand, manifestsToUpdate); if (_workloadInstaller.GetInstallationUnit().Equals(InstallationUnit.Packs)) { @@ -171,19 +193,31 @@ public void InstallWorkloads(IEnumerable workloadIds, bool skipManif _reporter.WriteLine(); } - private void InstallWorkloadsWithInstallRecord(IEnumerable workloadIds, SdkFeatureBand sdkFeatureBand) + private void InstallWorkloadsWithInstallRecord( + IEnumerable workloadIds, + SdkFeatureBand sdkFeatureBand, + IEnumerable<(ManifestId manifestId, ManifestVersion existingVersion, ManifestVersion newVersion)> manifestsToUpdate) { if (_workloadInstaller.GetInstallationUnit().Equals(InstallationUnit.Packs)) { var installer = _workloadInstaller.GetPackInstaller(); + IEnumerable workloadPackToInstall = new List(); - var workloadPackToInstall = workloadIds - .SelectMany(workloadId => _workloadResolver.GetPacksInWorkload(workloadId.ToString())) - .Distinct() - .Select(packId => _workloadResolver.TryGetPackInfo(packId)); TransactionalAction.Run( action: () => { + foreach (var manifest in manifestsToUpdate) + { + _workloadInstaller.InstallWorkloadManifest(manifest.manifestId, manifest.newVersion, sdkFeatureBand); + } + + _workloadResolver.RefreshWorkloadManifests(); + + workloadPackToInstall = workloadIds + .SelectMany(workloadId => _workloadResolver.GetPacksInWorkload(workloadId.ToString())) + .Distinct() + .Select(packId => _workloadResolver.TryGetPackInfo(packId)); + foreach (var packId in workloadPackToInstall) { installer.InstallWorkloadPack(packId, sdkFeatureBand); @@ -194,12 +228,17 @@ private void InstallWorkloadsWithInstallRecord(IEnumerable workloadI _workloadInstaller.GetWorkloadInstallationRecordRepository() .WriteWorkloadInstallationRecord(workloadId, sdkFeatureBand); } - }, rollback: () => { try { _reporter.WriteLine(LocalizableStrings.RollingBackInstall); + + foreach (var manifest in manifestsToUpdate) + { + _workloadInstaller.InstallWorkloadManifest(manifest.manifestId, manifest.existingVersion, sdkFeatureBand); + } + foreach (var packId in workloadPackToInstall) { installer.RollBackWorkloadPackInstall(packId, sdkFeatureBand); @@ -216,7 +255,6 @@ private void InstallWorkloadsWithInstallRecord(IEnumerable workloadI // Don't hide the original error if roll back fails _reporter.WriteLine(string.Format(LocalizableStrings.RollBackFailedMessage, e.Message)); } - }); } else diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallCommandParser.cs b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallCommandParser.cs index 34051eb58024..3f27fd17b44d 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallCommandParser.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallCommandParser.cs @@ -39,6 +39,9 @@ internal static class WorkloadInstallCommandParser IsHidden = true }; + public static readonly Option IncludePreviewOption = + new Option("--include-previews", LocalizableStrings.IncludePreviewOptionDescription); + public static readonly Option FromCacheOption = new Option("--from-cache", LocalizableStrings.FromCacheOptionDescription) { }; @@ -57,6 +60,7 @@ public static Command GetCommand() command.AddOption(SkipManifestUpdateOption); command.AddOption(PrintDownloadLinkOnlyOption); command.AddOption(FromCacheOption); + command.AddOption(IncludePreviewOption); command.AddOption(WorkloadCommandRestorePassThroughOptions.DisableParallelOption); command.AddOption(WorkloadCommandRestorePassThroughOptions.IgnoreFailedSourcesOption); command.AddOption(WorkloadCommandRestorePassThroughOptions.NoCacheOption); diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallRecords/IWorkloadInstallationRecordRepository.cs b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallRecords/IWorkloadInstallationRecordRepository.cs index c122614cb103..f38602ed62ad 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallRecords/IWorkloadInstallationRecordRepository.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallRecords/IWorkloadInstallationRecordRepository.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Workloads.Workload.Install.InstallRecord { internal interface IWorkloadInstallationRecordRepository { - IEnumerable GetInstalledWorkloads(SdkFeatureBand sdkFeatureBand); + IEnumerable GetInstalledWorkloads(SdkFeatureBand sdkFeatureBand); void WriteWorkloadInstallationRecord(WorkloadId workloadId, SdkFeatureBand sdkFeatureBand); diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallRecords/NetSdkManagedInstallationRecordInstaller.cs b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallRecords/NetSdkManagedInstallationRecordInstaller.cs index 3123112d5ba6..abe512208b58 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallRecords/NetSdkManagedInstallationRecordInstaller.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallRecords/NetSdkManagedInstallationRecordInstaller.cs @@ -32,17 +32,17 @@ public IEnumerable GetFeatureBandsWithInstallationRecords() } } - public IEnumerable GetInstalledWorkloads(SdkFeatureBand featureBand) + public IEnumerable GetInstalledWorkloads(SdkFeatureBand featureBand) { var path = Path.Combine(_workloadMetadataDir, featureBand.ToString(), _installedWorkloadDir); if (Directory.Exists(path)) { return Directory.EnumerateFiles(path) - .Select(file => Path.GetFileName(file)); + .Select(file => new WorkloadId(Path.GetFileName(file))); } else { - return new List(); + return new List(); } } diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadManifestUpdater.cs b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadManifestUpdater.cs new file mode 100644 index 000000000000..7505d2d2be30 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadManifestUpdater.cs @@ -0,0 +1,172 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.NET.Sdk.WorkloadManifestReader; +using System.IO; +using System.Linq; +using Microsoft.DotNet.Cli.NuGetPackageDownloader; +using Microsoft.DotNet.ToolPackage; +using System.Threading.Tasks; +using Microsoft.DotNet.Workloads.Workload.Install.InstallRecord; + +namespace Microsoft.DotNet.Workloads.Workload.Install +{ + internal class WorkloadManifestUpdater : IWorkloadManifestUpdater + { + private readonly IReporter _reporter; + private readonly IWorkloadManifestProvider _workloadManifestProvider; + private readonly INuGetPackageDownloader _nugetPackageDownloader; + private readonly string _userHome; + + public WorkloadManifestUpdater( + IReporter reporter, + IWorkloadManifestProvider workloadManifestProvider, + INuGetPackageDownloader nugetPackageDownloader, + string userHome) + { + _reporter = reporter; + _workloadManifestProvider = workloadManifestProvider; + _userHome = userHome; + _nugetPackageDownloader = nugetPackageDownloader; + } + + public async Task UpdateAdvertisingManifestsAsync(SdkFeatureBand featureBand, bool includePreviews) + { + var manifests = GetInstalledManifestIds(); + foreach (var manifest in manifests) + { + await UpdateAdvertisingManifestAsync(manifest, featureBand, includePreviews); + } + } + + public IEnumerable<(ManifestId manifestId, ManifestVersion existingVersion, ManifestVersion newVersion)> CalculateManifestUpdates(SdkFeatureBand featureBand) + { + var manifestUpdates = new List<(ManifestId, ManifestVersion, ManifestVersion)>(); + var currentManifestIds = GetInstalledManifestIds(); + foreach (var manifestId in currentManifestIds) + { + var currentManifestVersion = GetInstalledManifestVersion(manifestId); + var adManifestVersion = GetAdvertisingManifestVersion(featureBand, manifestId); + if (adManifestVersion == null) + { + continue; + } + + if (adManifestVersion != null && adManifestVersion.CompareTo(currentManifestVersion) > 0) + { + manifestUpdates.Add((manifestId, currentManifestVersion, adManifestVersion)); + } + } + return manifestUpdates; + } + + private IEnumerable GetInstalledManifestIds() + { + var manifestDirs = _workloadManifestProvider.GetManifestDirectories(); + + var manifests = new List(); + foreach (var manifestDir in manifestDirs) + { + var manifestId = Path.GetFileName(manifestDir); + manifests.Add(new ManifestId(manifestId)); + } + return manifests; + } + + private async Task UpdateAdvertisingManifestAsync(ManifestId manifestId, SdkFeatureBand featureBand, bool includePreviews) + { + string packagePath = null; + string extractionPath = null; + try + { + var adManifestPath = GetAdvertisingManifestPath(featureBand, manifestId); + packagePath = await _nugetPackageDownloader.DownloadPackageAsync(GetManifestPackageId(featureBand, manifestId), includePreview: includePreviews); + extractionPath = Path.Combine(_userHome, ".dotnet", "sdk-advertising-temp", $"{manifestId}-extracted"); + Directory.CreateDirectory(extractionPath); + var resultingFiles = await _nugetPackageDownloader.ExtractPackageAsync(packagePath, extractionPath); + + if (Directory.Exists(adManifestPath)) + { + Directory.Delete(adManifestPath, true); + } + Directory.CreateDirectory(Path.GetDirectoryName(adManifestPath)); + FileAccessRetrier.RetryOnMoveAccessFailure(() => Directory.Move(Path.Combine(extractionPath, "data"), adManifestPath)); + + _reporter.WriteLine(string.Format(LocalizableStrings.AdManifestUpdated, manifestId)); + } + catch (Exception e) + { + _reporter.WriteLine(string.Format(LocalizableStrings.FailedAdManifestUpdate, manifestId, e.Message)); + } + finally + { + if (!string.IsNullOrEmpty(extractionPath) && Directory.Exists(extractionPath)) + { + Directory.Delete(extractionPath, true); + } + + if (!string.IsNullOrEmpty(packagePath) && File.Exists(packagePath)) + { + File.Delete(packagePath); + } + + var versionDir = Path.GetDirectoryName(packagePath); + if (Directory.Exists(versionDir) && !Directory.GetFileSystemEntries(versionDir).Any()) + { + Directory.Delete(versionDir); + var idDir = Path.GetDirectoryName(versionDir); + if (Directory.Exists(idDir) && !Directory.GetFileSystemEntries(idDir).Any()) + { + Directory.Delete(idDir); + } + } + } + } + + private ManifestVersion GetAdvertisingManifestVersion(SdkFeatureBand featureBand, ManifestId manifestId) + { + var manifestPath = Path.Combine(GetAdvertisingManifestPath(featureBand, manifestId), "WorkloadManifest.json"); + if (!File.Exists(manifestPath)) + { + return null; + } + + using (FileStream fsSource = new FileStream(manifestPath, FileMode.Open, FileAccess.Read)) + { + var manifest = WorkloadManifestReader.ReadWorkloadManifest(manifestId.ToString(), fsSource); + return new ManifestVersion(manifest.Version); + } + } + + private ManifestVersion GetInstalledManifestVersion(ManifestId manifestId) + { + var manifestDir = _workloadManifestProvider.GetManifestDirectories() + .FirstOrDefault(dir => Path.GetFileName(dir).ToLowerInvariant().Equals(manifestId.ToString())); + if (manifestDir == null) + { + throw new Exception(string.Format(LocalizableStrings.ManifestDoesNotExist, manifestId.ToString())); + } + + var manifestPath = Path.Combine(manifestDir, "WorkloadManifest.json"); + if (!File.Exists(manifestPath)) + { + throw new Exception(string.Format(LocalizableStrings.ManifestDoesNotExist, manifestId.ToString())); + } + + using (FileStream fsSource = new FileStream(manifestPath, FileMode.Open, FileAccess.Read)) + { + var manifest = WorkloadManifestReader.ReadWorkloadManifest(manifestId.ToString(), fsSource); + return new ManifestVersion(manifest.Version); + } + } + + private string GetAdvertisingManifestPath(SdkFeatureBand featureBand, ManifestId manifestId) => + Path.Combine(_userHome, ".dotnet", "sdk-advertising", featureBand.ToString(), manifestId.ToString()); + + internal static PackageId GetManifestPackageId(SdkFeatureBand featureBand, ManifestId manifestId) => + new PackageId($"{manifestId}.Manifest-{featureBand}"); + } +} diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.cs.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.cs.xlf index c51f4cbdf4bd..7b23ab94fb4c 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.cs.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.cs.xlf @@ -2,6 +2,21 @@ + + Updated advertising manifest {0}. + Updated advertising manifest {0}. + + + + Failed to update the advertising manifest {0}: {1} + Failed to update the advertising manifest {0}: {1} + + + + Failed to install manifest {0} version {1}: {2}. + Failed to install manifest {0} version {1}: {2}. + + Complete the operation from cache (offline). Complete the operation from cache (offline). @@ -12,6 +27,11 @@ Garbage collecting for SDK feature bands {0}... + + Allow prerelease workload manifests. + Allow prerelease workload manifests. + + Successfully installed workload(s) {0}. Successfully installed workload(s) {0}. @@ -22,6 +42,11 @@ Installing pack {0} version {1}... + + Installing workload manifest {0} version {1}. + Installing workload manifest {0} version {1}. + + The settings file in the workload's NuGet package is invalid: {0} The settings file in the workload's NuGet package is invalid: {0} @@ -42,6 +67,11 @@ Cílová architektura, pro kterou se má nástroj nainstalovat + + No manifest with id {0} exists. + No manifest with id {0} exists. + + Installation roll back failed: {0} Installation roll back failed: {0} diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.de.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.de.xlf index 83112c5a9745..7a06b5d1b702 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.de.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.de.xlf @@ -2,6 +2,21 @@ + + Updated advertising manifest {0}. + Updated advertising manifest {0}. + + + + Failed to update the advertising manifest {0}: {1} + Failed to update the advertising manifest {0}: {1} + + + + Failed to install manifest {0} version {1}: {2}. + Failed to install manifest {0} version {1}: {2}. + + Complete the operation from cache (offline). Complete the operation from cache (offline). @@ -12,6 +27,11 @@ Garbage collecting for SDK feature bands {0}... + + Allow prerelease workload manifests. + Allow prerelease workload manifests. + + Successfully installed workload(s) {0}. Successfully installed workload(s) {0}. @@ -22,6 +42,11 @@ Installing pack {0} version {1}... + + Installing workload manifest {0} version {1}. + Installing workload manifest {0} version {1}. + + The settings file in the workload's NuGet package is invalid: {0} The settings file in the workload's NuGet package is invalid: {0} @@ -42,6 +67,11 @@ Das Zielframework, für das das Tool installiert wird. + + No manifest with id {0} exists. + No manifest with id {0} exists. + + Installation roll back failed: {0} Installation roll back failed: {0} diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.es.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.es.xlf index aaa4936958ac..f606f3b78c10 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.es.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.es.xlf @@ -2,6 +2,21 @@ + + Updated advertising manifest {0}. + Updated advertising manifest {0}. + + + + Failed to update the advertising manifest {0}: {1} + Failed to update the advertising manifest {0}: {1} + + + + Failed to install manifest {0} version {1}: {2}. + Failed to install manifest {0} version {1}: {2}. + + Complete the operation from cache (offline). Complete the operation from cache (offline). @@ -12,6 +27,11 @@ Garbage collecting for SDK feature bands {0}... + + Allow prerelease workload manifests. + Allow prerelease workload manifests. + + Successfully installed workload(s) {0}. Successfully installed workload(s) {0}. @@ -22,6 +42,11 @@ Installing pack {0} version {1}... + + Installing workload manifest {0} version {1}. + Installing workload manifest {0} version {1}. + + The settings file in the workload's NuGet package is invalid: {0} The settings file in the workload's NuGet package is invalid: {0} @@ -42,6 +67,11 @@ La plataforma de destino para la que se instala la herramienta. + + No manifest with id {0} exists. + No manifest with id {0} exists. + + Installation roll back failed: {0} Installation roll back failed: {0} diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.fr.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.fr.xlf index ba82634ccb4d..1714f55b708d 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.fr.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.fr.xlf @@ -2,6 +2,21 @@ + + Updated advertising manifest {0}. + Updated advertising manifest {0}. + + + + Failed to update the advertising manifest {0}: {1} + Failed to update the advertising manifest {0}: {1} + + + + Failed to install manifest {0} version {1}: {2}. + Failed to install manifest {0} version {1}: {2}. + + Complete the operation from cache (offline). Complete the operation from cache (offline). @@ -12,6 +27,11 @@ Garbage collecting for SDK feature bands {0}... + + Allow prerelease workload manifests. + Allow prerelease workload manifests. + + Successfully installed workload(s) {0}. Successfully installed workload(s) {0}. @@ -22,6 +42,11 @@ Installing pack {0} version {1}... + + Installing workload manifest {0} version {1}. + Installing workload manifest {0} version {1}. + + The settings file in the workload's NuGet package is invalid: {0} The settings file in the workload's NuGet package is invalid: {0} @@ -42,6 +67,11 @@ Framework cible pour lequel l'outil doit être installé. + + No manifest with id {0} exists. + No manifest with id {0} exists. + + Installation roll back failed: {0} Installation roll back failed: {0} diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.it.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.it.xlf index eb3a22a67bac..51ba90260af4 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.it.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.it.xlf @@ -2,6 +2,21 @@ + + Updated advertising manifest {0}. + Updated advertising manifest {0}. + + + + Failed to update the advertising manifest {0}: {1} + Failed to update the advertising manifest {0}: {1} + + + + Failed to install manifest {0} version {1}: {2}. + Failed to install manifest {0} version {1}: {2}. + + Complete the operation from cache (offline). Complete the operation from cache (offline). @@ -12,6 +27,11 @@ Garbage collecting for SDK feature bands {0}... + + Allow prerelease workload manifests. + Allow prerelease workload manifests. + + Successfully installed workload(s) {0}. Successfully installed workload(s) {0}. @@ -22,6 +42,11 @@ Installing pack {0} version {1}... + + Installing workload manifest {0} version {1}. + Installing workload manifest {0} version {1}. + + The settings file in the workload's NuGet package is invalid: {0} The settings file in the workload's NuGet package is invalid: {0} @@ -42,6 +67,11 @@ Framework di destinazione per cui installare lo strumento. + + No manifest with id {0} exists. + No manifest with id {0} exists. + + Installation roll back failed: {0} Installation roll back failed: {0} diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ja.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ja.xlf index 04f0d6bdb6dd..a6147473a0f9 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ja.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ja.xlf @@ -2,6 +2,21 @@ + + Updated advertising manifest {0}. + Updated advertising manifest {0}. + + + + Failed to update the advertising manifest {0}: {1} + Failed to update the advertising manifest {0}: {1} + + + + Failed to install manifest {0} version {1}: {2}. + Failed to install manifest {0} version {1}: {2}. + + Complete the operation from cache (offline). Complete the operation from cache (offline). @@ -12,6 +27,11 @@ Garbage collecting for SDK feature bands {0}... + + Allow prerelease workload manifests. + Allow prerelease workload manifests. + + Successfully installed workload(s) {0}. Successfully installed workload(s) {0}. @@ -22,6 +42,11 @@ Installing pack {0} version {1}... + + Installing workload manifest {0} version {1}. + Installing workload manifest {0} version {1}. + + The settings file in the workload's NuGet package is invalid: {0} The settings file in the workload's NuGet package is invalid: {0} @@ -42,6 +67,11 @@ ツールをインストールするターゲット フレームワーク。 + + No manifest with id {0} exists. + No manifest with id {0} exists. + + Installation roll back failed: {0} Installation roll back failed: {0} diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ko.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ko.xlf index ed91f8f0faa5..f8741db805d2 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ko.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ko.xlf @@ -2,6 +2,21 @@ + + Updated advertising manifest {0}. + Updated advertising manifest {0}. + + + + Failed to update the advertising manifest {0}: {1} + Failed to update the advertising manifest {0}: {1} + + + + Failed to install manifest {0} version {1}: {2}. + Failed to install manifest {0} version {1}: {2}. + + Complete the operation from cache (offline). Complete the operation from cache (offline). @@ -12,6 +27,11 @@ Garbage collecting for SDK feature bands {0}... + + Allow prerelease workload manifests. + Allow prerelease workload manifests. + + Successfully installed workload(s) {0}. Successfully installed workload(s) {0}. @@ -22,6 +42,11 @@ Installing pack {0} version {1}... + + Installing workload manifest {0} version {1}. + Installing workload manifest {0} version {1}. + + The settings file in the workload's NuGet package is invalid: {0} The settings file in the workload's NuGet package is invalid: {0} @@ -42,6 +67,11 @@ 도구를 설치할 대상 프레임워크입니다. + + No manifest with id {0} exists. + No manifest with id {0} exists. + + Installation roll back failed: {0} Installation roll back failed: {0} diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.pl.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.pl.xlf index 94ea5c2185b7..e478c43078b5 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.pl.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.pl.xlf @@ -2,6 +2,21 @@ + + Updated advertising manifest {0}. + Updated advertising manifest {0}. + + + + Failed to update the advertising manifest {0}: {1} + Failed to update the advertising manifest {0}: {1} + + + + Failed to install manifest {0} version {1}: {2}. + Failed to install manifest {0} version {1}: {2}. + + Complete the operation from cache (offline). Complete the operation from cache (offline). @@ -12,6 +27,11 @@ Garbage collecting for SDK feature bands {0}... + + Allow prerelease workload manifests. + Allow prerelease workload manifests. + + Successfully installed workload(s) {0}. Successfully installed workload(s) {0}. @@ -22,6 +42,11 @@ Installing pack {0} version {1}... + + Installing workload manifest {0} version {1}. + Installing workload manifest {0} version {1}. + + The settings file in the workload's NuGet package is invalid: {0} The settings file in the workload's NuGet package is invalid: {0} @@ -42,6 +67,11 @@ Docelowa platforma, dla której ma zostać zainstalowane narzędzie. + + No manifest with id {0} exists. + No manifest with id {0} exists. + + Installation roll back failed: {0} Installation roll back failed: {0} diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.pt-BR.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.pt-BR.xlf index a5637484a7ca..b8dfd1e3819f 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.pt-BR.xlf @@ -2,6 +2,21 @@ + + Updated advertising manifest {0}. + Updated advertising manifest {0}. + + + + Failed to update the advertising manifest {0}: {1} + Failed to update the advertising manifest {0}: {1} + + + + Failed to install manifest {0} version {1}: {2}. + Failed to install manifest {0} version {1}: {2}. + + Complete the operation from cache (offline). Complete the operation from cache (offline). @@ -12,6 +27,11 @@ Garbage collecting for SDK feature bands {0}... + + Allow prerelease workload manifests. + Allow prerelease workload manifests. + + Successfully installed workload(s) {0}. Successfully installed workload(s) {0}. @@ -22,6 +42,11 @@ Installing pack {0} version {1}... + + Installing workload manifest {0} version {1}. + Installing workload manifest {0} version {1}. + + The settings file in the workload's NuGet package is invalid: {0} The settings file in the workload's NuGet package is invalid: {0} @@ -42,6 +67,11 @@ A estrutura de destino para a qual instalar a ferramenta. + + No manifest with id {0} exists. + No manifest with id {0} exists. + + Installation roll back failed: {0} Installation roll back failed: {0} diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ru.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ru.xlf index b215d10b1ebc..87b96f903fd1 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ru.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ru.xlf @@ -2,6 +2,21 @@ + + Updated advertising manifest {0}. + Updated advertising manifest {0}. + + + + Failed to update the advertising manifest {0}: {1} + Failed to update the advertising manifest {0}: {1} + + + + Failed to install manifest {0} version {1}: {2}. + Failed to install manifest {0} version {1}: {2}. + + Complete the operation from cache (offline). Complete the operation from cache (offline). @@ -12,6 +27,11 @@ Garbage collecting for SDK feature bands {0}... + + Allow prerelease workload manifests. + Allow prerelease workload manifests. + + Successfully installed workload(s) {0}. Successfully installed workload(s) {0}. @@ -22,6 +42,11 @@ Installing pack {0} version {1}... + + Installing workload manifest {0} version {1}. + Installing workload manifest {0} version {1}. + + The settings file in the workload's NuGet package is invalid: {0} The settings file in the workload's NuGet package is invalid: {0} @@ -42,6 +67,11 @@ Целевая платформа для установки инструмента. + + No manifest with id {0} exists. + No manifest with id {0} exists. + + Installation roll back failed: {0} Installation roll back failed: {0} diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.tr.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.tr.xlf index bd665139337c..b1e8b69a4369 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.tr.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.tr.xlf @@ -2,6 +2,21 @@ + + Updated advertising manifest {0}. + Updated advertising manifest {0}. + + + + Failed to update the advertising manifest {0}: {1} + Failed to update the advertising manifest {0}: {1} + + + + Failed to install manifest {0} version {1}: {2}. + Failed to install manifest {0} version {1}: {2}. + + Complete the operation from cache (offline). Complete the operation from cache (offline). @@ -12,6 +27,11 @@ Garbage collecting for SDK feature bands {0}... + + Allow prerelease workload manifests. + Allow prerelease workload manifests. + + Successfully installed workload(s) {0}. Successfully installed workload(s) {0}. @@ -22,6 +42,11 @@ Installing pack {0} version {1}... + + Installing workload manifest {0} version {1}. + Installing workload manifest {0} version {1}. + + The settings file in the workload's NuGet package is invalid: {0} The settings file in the workload's NuGet package is invalid: {0} @@ -42,6 +67,11 @@ Aracın yükleneceği hedef çerçeve. + + No manifest with id {0} exists. + No manifest with id {0} exists. + + Installation roll back failed: {0} Installation roll back failed: {0} diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.zh-Hans.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.zh-Hans.xlf index 925e0830e36f..a43362149251 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.zh-Hans.xlf @@ -2,6 +2,21 @@ + + Updated advertising manifest {0}. + Updated advertising manifest {0}. + + + + Failed to update the advertising manifest {0}: {1} + Failed to update the advertising manifest {0}: {1} + + + + Failed to install manifest {0} version {1}: {2}. + Failed to install manifest {0} version {1}: {2}. + + Complete the operation from cache (offline). Complete the operation from cache (offline). @@ -12,6 +27,11 @@ Garbage collecting for SDK feature bands {0}... + + Allow prerelease workload manifests. + Allow prerelease workload manifests. + + Successfully installed workload(s) {0}. Successfully installed workload(s) {0}. @@ -22,6 +42,11 @@ Installing pack {0} version {1}... + + Installing workload manifest {0} version {1}. + Installing workload manifest {0} version {1}. + + The settings file in the workload's NuGet package is invalid: {0} The settings file in the workload's NuGet package is invalid: {0} @@ -42,6 +67,11 @@ 要安装工具的目标框架。 + + No manifest with id {0} exists. + No manifest with id {0} exists. + + Installation roll back failed: {0} Installation roll back failed: {0} diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.zh-Hant.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.zh-Hant.xlf index 0d8b78c5bb2b..8573aefda02c 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.zh-Hant.xlf @@ -2,6 +2,21 @@ + + Updated advertising manifest {0}. + Updated advertising manifest {0}. + + + + Failed to update the advertising manifest {0}: {1} + Failed to update the advertising manifest {0}: {1} + + + + Failed to install manifest {0} version {1}: {2}. + Failed to install manifest {0} version {1}: {2}. + + Complete the operation from cache (offline). Complete the operation from cache (offline). @@ -12,6 +27,11 @@ Garbage collecting for SDK feature bands {0}... + + Allow prerelease workload manifests. + Allow prerelease workload manifests. + + Successfully installed workload(s) {0}. Successfully installed workload(s) {0}. @@ -22,6 +42,11 @@ Installing pack {0} version {1}... + + Installing workload manifest {0} version {1}. + Installing workload manifest {0} version {1}. + + The settings file in the workload's NuGet package is invalid: {0} The settings file in the workload's NuGet package is invalid: {0} @@ -42,6 +67,11 @@ 要為工具安裝的目標 Framework。 + + No manifest with id {0} exists. + No manifest with id {0} exists. + + Installation roll back failed: {0} Installation roll back failed: {0} diff --git a/src/Cli/dotnet/dotnet.csproj b/src/Cli/dotnet/dotnet.csproj index ef20e1cce97a..4df160046b8f 100644 --- a/src/Cli/dotnet/dotnet.csproj +++ b/src/Cli/dotnet/dotnet.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/IWorkloadResolver.cs b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/IWorkloadResolver.cs index e6f12be05d00..90c5cbf719a0 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/IWorkloadResolver.cs +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/IWorkloadResolver.cs @@ -21,5 +21,10 @@ public interface IWorkloadResolver /// A workload pack ID /// Information about the workload pack, or null if the specified pack ID isn't found in the manifests WorkloadResolver.PackInfo? TryGetPackInfo(string packId); + + /// + /// Refresh workload and pack information based on the current installed workload manifest files + /// + void RefreshWorkloadManifests(); } } diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/WorkloadResolver.cs b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/WorkloadResolver.cs index 37f5529f92a7..80f9a3ce52de 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/WorkloadResolver.cs +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/WorkloadResolver.cs @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; - using Microsoft.DotNet.MSBuildSdkResolver; namespace Microsoft.NET.Sdk.WorkloadManifestReader @@ -20,6 +19,7 @@ public class WorkloadResolver : IWorkloadResolver { private readonly Dictionary _workloads = new Dictionary(); private readonly Dictionary _packs = new Dictionary(); + private readonly IWorkloadManifestProvider _manifestProvider; private string[] _currentRuntimeIdentifiers; private readonly string [] _dotnetRootPaths; @@ -61,10 +61,20 @@ private WorkloadResolver(IWorkloadManifestProvider manifestProvider, string [] d { _dotnetRootPaths = dotnetRootPaths; _currentRuntimeIdentifiers = currentRuntimeIdentifiers; + _manifestProvider = manifestProvider; + + RefreshWorkloadManifests(); + } + + public void RefreshWorkloadManifests() + { + _workloads.Clear(); + _packs.Clear(); var manifests = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach ((string manifestId, Stream manifestStream) in manifestProvider.GetManifests()) + foreach ((string manifestId, Stream manifestStream) in _manifestProvider.GetManifests()) + { using (manifestStream) { diff --git a/src/Tests/dotnet-workload-install.Tests/GivenDotnetWorkloadInstall.cs b/src/Tests/dotnet-workload-install.Tests/GivenDotnetWorkloadInstall.cs index 3c5a43214dbb..3e84e99e5443 100644 --- a/src/Tests/dotnet-workload-install.Tests/GivenDotnetWorkloadInstall.cs +++ b/src/Tests/dotnet-workload-install.Tests/GivenDotnetWorkloadInstall.cs @@ -2,10 +2,14 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; +using System.CommandLine.Parsing; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using FluentAssertions; using ManifestReaderTests; +using Microsoft.DotNet.Cli.NuGetPackageDownloader; using Microsoft.DotNet.Workloads.Workload.Install; using Microsoft.NET.Sdk.WorkloadManifestReader; using Microsoft.NET.TestFramework; @@ -48,12 +52,8 @@ public void GivenWorkloadInstallItErrorsOnFakeWorkloadName() public void GivenWorkloadInstallItCanInstallPacks() { var mockWorkloadIds = new WorkloadId[] { new WorkloadId("xamarin-android") }; - var testDirectory = _testAssetsManager.CreateTestDirectory().Path; - var dotnetRoot = Path.Combine(testDirectory, "dotnet"); - var installer = new MockPackWorkloadInstaller(); - var workloadResolver = WorkloadResolver.CreateForTests(new MockManifestProvider(new[] { _manifestPath }), new string[] { dotnetRoot }); var parseResult = Parser.GetWorkloadsInstance.Parse(new string[] { "dotnet", "workload", "install", "xamarin-android", "--skip-manifest-update" }); - var installManager = new WorkloadInstallCommand(parseResult, reporter: _reporter, workloadResolver: workloadResolver, workloadInstaller: installer, version: "6.0.100"); + (_, var installManager, var installer, _, _) = GetTestInstallers(parseResult); installManager.InstallWorkloads(mockWorkloadIds, true); @@ -61,19 +61,14 @@ public void GivenWorkloadInstallItCanInstallPacks() installer.InstallationRecordRepository.WorkloadInstallRecord.Should().BeEquivalentTo(mockWorkloadIds); installer.InstalledPacks.Count.Should().Be(8); installer.InstalledPacks.Where(pack => pack.Id.Contains("Android")).Count().Should().Be(8); - _reporter.Lines.Contains(string.Format(LocalizableStrings.InstallationSucceeded, "xamarin-android")); } [Fact] public void GivenWorkloadInstallItCanRollBackPackInstallation() { var mockWorkloadIds = new WorkloadId[] { new WorkloadId("xamarin-android"), new WorkloadId("xamarin-android-build") }; - var testDirectory = _testAssetsManager.CreateTestDirectory().Path; - var dotnetRoot = Path.Combine(testDirectory, "dotnet"); - var installer = new MockPackWorkloadInstaller(failingWorkload: "xamarin-android-build"); - var workloadResolver = WorkloadResolver.CreateForTests(new MockManifestProvider(new[] { _manifestPath }), new string[] { dotnetRoot }); var parseResult = Parser.GetWorkloadsInstance.Parse(new string[] { "dotnet", "workload", "install", "xamarin-android", "xamarin-android-build", "--skip-manifest-update" }); - var installManager = new WorkloadInstallCommand(parseResult, reporter: _reporter, workloadResolver: workloadResolver, workloadInstaller: installer, version: "6.0.100"); + (_, var installManager, var installer, var workloadResolver, _) = GetTestInstallers(parseResult, failingWorkload: "xamarin-android-build"); var exceptionThrown = Assert.Throws(() => installManager.InstallWorkloads(mockWorkloadIds, true)); exceptionThrown.Message.Should().Be("Failing workload: xamarin-android-build"); @@ -101,6 +96,53 @@ public void GivenWorkloadInstallOnFailingRollbackItDisplaysTopLevelError() exceptionThrown.Message.Should().Be("Failing workload: xamarin-android-build"); string.Join(" ", _reporter.Lines).Should().Contain("Rollback failure"); + } + + [Fact] + public void GivenWorkloadInstallItCanUpdateAdvertisingManifests() + { + var parseResult = Parser.GetWorkloadsInstance.Parse(new string[] { "dotnet", "workload", "install", "xamarin-android" }); + (_, var installManager, var installer, _, var manifestUpdater) = GetTestInstallers(parseResult); + + installManager.InstallWorkloads(new List(), false); // Don't actually do any installs, just update manifests + + installer.InstalledManifests.Should().BeEmpty(); // Didn't try to alter any installed manifests + manifestUpdater.CalculateManifestUpdatesCallParams.Should().BeEquivalentTo(new SdkFeatureBand[] { new SdkFeatureBand("6.0.100") }); + manifestUpdater.UpdateAdvertisingManifestsCallParams.Should().BeEquivalentTo(new SdkFeatureBand[] { new SdkFeatureBand("6.0.100") }); + } + + [Fact] + public void GivenWorkloadInstallItCanUpdateInstalledManifests() + { + var parseResult = Parser.GetWorkloadsInstance.Parse(new string[] { "dotnet", "workload", "install", "xamarin-android" }); + var manifestsToUpdate = new (ManifestId, ManifestVersion, ManifestVersion)[] { }; + (_, var installManager, var installer, _, _) = GetTestInstallers(parseResult, manifestUpdates: manifestsToUpdate); + + installManager.InstallWorkloads(new List(), false); // Don't actually do any installs, just update manifests + + installer.InstalledManifests.Should().BeEquivalentTo(manifestsToUpdate); + } + + private (string, WorkloadInstallCommand, MockPackWorkloadInstaller, IWorkloadResolver, MockWorkloadManifestUpdater) GetTestInstallers( + ParseResult parseResult, [CallerMemberName] string testName = "", string failingWorkload = null, IEnumerable<(ManifestId, ManifestVersion, ManifestVersion)> manifestUpdates = null) + { + var testDirectory = _testAssetsManager.CreateTestDirectory(testName: testName).Path; + var dotnetRoot = Path.Combine(testDirectory, "dotnet"); + var installer = new MockPackWorkloadInstaller(failingWorkload); + var workloadResolver = WorkloadResolver.CreateForTests(new MockManifestProvider(new[] { _manifestPath }), new string[] { dotnetRoot }); + var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot); + var manifestUpdater = new MockWorkloadManifestUpdater(manifestUpdates); + var installManager = new WorkloadInstallCommand( + parseResult, + reporter: _reporter, + workloadResolver: workloadResolver, + workloadInstaller: installer, + nugetPackageDownloader: nugetDownloader, + workloadManifestUpdater: manifestUpdater, + userHome: testDirectory, + version: "6.0.100"); + + return (testDirectory, installManager, installer, workloadResolver, manifestUpdater); } } } diff --git a/src/Tests/dotnet-workload-install.Tests/GivenNetSdkManagedWorkloadInstall.cs b/src/Tests/dotnet-workload-install.Tests/GivenNetSdkManagedWorkloadInstall.cs index 1fe1693731fa..abc30118bb47 100644 --- a/src/Tests/dotnet-workload-install.Tests/GivenNetSdkManagedWorkloadInstall.cs +++ b/src/Tests/dotnet-workload-install.Tests/GivenNetSdkManagedWorkloadInstall.cs @@ -39,34 +39,39 @@ public void GivenManagedInstallItsInsallationUnitIsPacks() installer.GetInstallationUnit().Should().Be(InstallationUnit.Packs); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void GivenManagedInstallItCanGetFeatureBands(bool writeRecords) + [Fact] + public void GivenManagedInstallItCanGetFeatureBandsWhenFilesArePresent() { var versions = new string[] { "6.0.100", "6.0.300", "7.0.100" }; - var (dotnetRoot, installer, _) = GetTestInstaller(identifier: writeRecords.ToString()); + var (dotnetRoot, installer, _) = GetTestInstaller(); // Write fake workloads foreach (var version in versions) { var path = Path.Combine(dotnetRoot, "metadata", "workloads", version, "InstalledWorkloads"); Directory.CreateDirectory(path); - if (writeRecords) - { - File.Create(Path.Combine(path, "6.0.100")); - } + File.Create(Path.Combine(path, "6.0.100")); } var featureBands = installer.GetWorkloadInstallationRecordRepository().GetFeatureBandsWithInstallationRecords(); - if (writeRecords) - { - featureBands.ShouldBeEquivalentTo(versions); - } - else + featureBands.ShouldBeEquivalentTo(versions); + } + + [Fact] + public void GivenManagedInstallItCanNotGetFeatureBandsWhenFilesAreNotPresent() + { + var versions = new string[] { "6.0.100", "6.0.300", "7.0.100" }; + var (dotnetRoot, installer, _) = GetTestInstaller(); + + // Write fake workloads + foreach (var version in versions) { - featureBands.Should().BeEmpty(); + var path = Path.Combine(dotnetRoot, "metadata", "workloads", version, "InstalledWorkloads"); + Directory.CreateDirectory(path); } + + var featureBands = installer.GetWorkloadInstallationRecordRepository().GetFeatureBandsWithInstallationRecords(); + featureBands.Should().BeEmpty(); } [Fact] @@ -111,10 +116,10 @@ public void GivenManagedInstallItCanInstallDirectoryPacks() installer.InstallWorkloadPack(packInfo, new SdkFeatureBand(version)); var mockNugetInstaller = nugetInstaller as MockNuGetPackageDownloader; - mockNugetInstaller.InstallCallParams.Count.Should().Be(1); - mockNugetInstaller.InstallCallParams[0].ShouldBeEquivalentTo((new PackageId(packInfo.Id), new NuGetVersion(packInfo.Version))); + mockNugetInstaller.DownloadCallParams.Count.Should().Be(1); + mockNugetInstaller.DownloadCallParams[0].ShouldBeEquivalentTo((new PackageId(packInfo.Id), new NuGetVersion(packInfo.Version))); mockNugetInstaller.ExtractCallParams.Count.Should().Be(1); - mockNugetInstaller.ExtractCallParams[0].ShouldBeEquivalentTo((mockNugetInstaller.InstallCallResult[0], Path.Combine(dotnetRoot, "metadata", "temp", $"{packInfo.Id}-{packInfo.Version}-extracted"))); + mockNugetInstaller.ExtractCallParams[0].ShouldBeEquivalentTo((mockNugetInstaller.DownloadCallResult[0], Path.Combine(dotnetRoot, "metadata", "temp", $"{packInfo.Id}-{packInfo.Version}-extracted"))); var installationRecordPath = Path.Combine(dotnetRoot, "metadata", "workloads", "InstalledPacks", "v1", packInfo.Id, packInfo.Version, version); File.Exists(installationRecordPath).Should().BeTrue(); @@ -130,8 +135,8 @@ public void GivenManagedInstallItCanInstallSingleFilePacks() var version = "6.0.100"; installer.InstallWorkloadPack(packInfo, new SdkFeatureBand(version)); - (nugetInstaller as MockNuGetPackageDownloader).InstallCallParams.Count.Should().Be(1); - (nugetInstaller as MockNuGetPackageDownloader).InstallCallParams[0].ShouldBeEquivalentTo((new PackageId(packInfo.Id), new NuGetVersion(packInfo.Version))); + (nugetInstaller as MockNuGetPackageDownloader).DownloadCallParams.Count.Should().Be(1); + (nugetInstaller as MockNuGetPackageDownloader).DownloadCallParams[0].ShouldBeEquivalentTo((new PackageId(packInfo.Id), new NuGetVersion(packInfo.Version))); (nugetInstaller as MockNuGetPackageDownloader).ExtractCallParams.Count.Should().Be(0); var installationRecordPath = Path.Combine(dotnetRoot, "metadata", "workloads", "InstalledPacks", "v1", packInfo.Id, packInfo.Version, version); @@ -149,8 +154,8 @@ public void GivenManagedInstallItCanInstallPacksWithAliases() var version = "6.0.100"; installer.InstallWorkloadPack(packInfo, new SdkFeatureBand(version)); - (nugetInstaller as MockNuGetPackageDownloader).InstallCallParams.Count.Should().Be(1); - (nugetInstaller as MockNuGetPackageDownloader).InstallCallParams[0].ShouldBeEquivalentTo((new PackageId(alias), new NuGetVersion(packInfo.Version))); + (nugetInstaller as MockNuGetPackageDownloader).DownloadCallParams.Count.Should().Be(1); + (nugetInstaller as MockNuGetPackageDownloader).DownloadCallParams[0].ShouldBeEquivalentTo((new PackageId(alias), new NuGetVersion(packInfo.Version))); } [Fact] @@ -165,7 +170,7 @@ public void GivenManagedInstallItDetectsInstalledPacks() installer.InstallWorkloadPack(packInfo, new SdkFeatureBand(version)); - (nugetInstaller as MockNuGetPackageDownloader).InstallCallParams.Count.Should().Be(0); + (nugetInstaller as MockNuGetPackageDownloader).DownloadCallParams.Count.Should().Be(0); } [Fact] @@ -283,11 +288,26 @@ public void GivenManagedInstallItDoesNotRemovePacksWithInstallRecords() } } - private (string, NetSdkManagedInstaller, INuGetPackageDownloader) GetTestInstaller([CallerMemberName] string testName = "", bool failingInstaller = false, string identifier = "") + [Fact] + public void GivenManagedInstallItCanInstallManifestVersion() + { + var (_, installer, nugetDownloader) = GetTestInstaller(manifestDownload: true); + var featureBand = new SdkFeatureBand("6.0.100"); + var manifestId = new ManifestId("test-manifest-1"); + var manifestVersion = new ManifestVersion("5.0.0"); + + installer.InstallWorkloadManifest(manifestId, manifestVersion, featureBand); + + var mockNugetInstaller = nugetDownloader as MockNuGetPackageDownloader; + mockNugetInstaller.DownloadCallParams.Count.Should().Be(1); + mockNugetInstaller.DownloadCallParams[0].ShouldBeEquivalentTo((new PackageId($"{manifestId}.manifest-{featureBand}"), new NuGetVersion(manifestVersion.ToString()))); + } + + private (string, NetSdkManagedInstaller, INuGetPackageDownloader) GetTestInstaller([CallerMemberName] string testName = "", bool failingInstaller = false, string identifier = "", bool manifestDownload = false) { var testDirectory = _testAssetsManager.CreateTestDirectory(testName, identifier: identifier).Path; var dotnetRoot = Path.Combine(testDirectory, "dotnet"); - INuGetPackageDownloader nugetInstaller = failingInstaller ? new FailingNuGetPackageDownloader(testDirectory) : new MockNuGetPackageDownloader(dotnetRoot); + INuGetPackageDownloader nugetInstaller = failingInstaller ? new FailingNuGetPackageDownloader(testDirectory) : new MockNuGetPackageDownloader(dotnetRoot, manifestDownload); var workloadResolver = WorkloadResolver.CreateForTests(new MockManifestProvider(new[] { _manifestPath }), new string[] { dotnetRoot }); var sdkFeatureBand = new SdkFeatureBand("6.0.100"); return (dotnetRoot, new NetSdkManagedInstaller(_reporter, sdkFeatureBand, workloadResolver, nugetInstaller, dotnetRoot), nugetInstaller); diff --git a/src/Tests/dotnet-workload-install.Tests/GivenWorkloadManifestUpdater.cs b/src/Tests/dotnet-workload-install.Tests/GivenWorkloadManifestUpdater.cs new file mode 100644 index 000000000000..5f2b4714662f --- /dev/null +++ b/src/Tests/dotnet-workload-install.Tests/GivenWorkloadManifestUpdater.cs @@ -0,0 +1,116 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; +using System.Linq; +using FluentAssertions; +using ManifestReaderTests; +using Microsoft.DotNet.Cli.NuGetPackageDownloader; +using Microsoft.DotNet.ToolPackage; +using Microsoft.DotNet.Workloads.Workload.Install; +using Microsoft.NET.TestFramework; +using Microsoft.NET.TestFramework.Utilities; +using NuGet.Versioning; +using Xunit; +using Xunit.Abstractions; +using Microsoft.DotNet.Workloads.Workload.Install.InstallRecord; + +namespace Microsoft.DotNet.Cli.Workload.Install.Tests +{ + public class GivenWorkloadManifestUpdater : SdkTest + { + private readonly BufferedReporter _reporter; + private readonly string _manifestFileName = "WorkloadManifest.json"; + + public GivenWorkloadManifestUpdater(ITestOutputHelper log) : base(log) + { + _reporter = new BufferedReporter(); + } + + [Fact] + public void GivenWorkloadManifestUpdateItCanUpdateAdvertisingManifests() + { + var testDir = _testAssetsManager.CreateTestDirectory().Path; + var featureBand = "6.0.100"; + var dotnetRoot = Path.Combine(testDir, "dotnet"); + var installedManifests = new ManifestId[] { new ManifestId("test-manifest-1"), new ManifestId("test-manifest-2"), new ManifestId("test-manifest-3") }; + + // Write mock manifests + var installedManifestDir = Path.Combine(testDir, "dotnet", "sdk-manifests", featureBand); + var adManifestDir = Path.Combine(testDir, ".dotnet", "sdk-advertising", featureBand); + Directory.CreateDirectory(installedManifestDir); + Directory.CreateDirectory(adManifestDir); + foreach (var manifest in installedManifests) + { + Directory.CreateDirectory(Path.Combine(installedManifestDir, manifest.ToString())); + File.WriteAllText(Path.Combine(installedManifestDir, manifest.ToString(), _manifestFileName), GetManifestContent(new ManifestVersion("1.0.0"))); + } + + var manifestDirs = installedManifests + .Select(manifest => Path.Combine(installedManifestDir, manifest.ToString(), _manifestFileName)) + .ToArray(); + var workloadManifestProvider = new MockManifestProvider(manifestDirs); + var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot); + var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadManifestProvider, nugetDownloader, testDir); + + manifestUpdater.UpdateAdvertisingManifestsAsync(new SdkFeatureBand(featureBand), true).Wait(); + var expectedDownloadedPackages = installedManifests.Select(id => ((PackageId, NuGetVersion))(new PackageId($"{id}.manifest-{featureBand}"), null)); + nugetDownloader.DownloadCallParams.Should().BeEquivalentTo(expectedDownloadedPackages); + } + + [Fact] + public void GivenWorkloadManifestUpdateItCanCalculateUpdates() + { + var testDir = _testAssetsManager.CreateTestDirectory().Path; + var featureBand = "6.0.100"; + var dotnetRoot = Path.Combine(testDir, "dotnet"); + var expectedManifestUpdates = new (ManifestId, ManifestVersion, ManifestVersion)[] { + (new ManifestId("test-manifest-1"), new ManifestVersion("5.0.0"), new ManifestVersion("7.0.0")), + (new ManifestId("test-manifest-2"), new ManifestVersion("3.0.0"), new ManifestVersion("4.0.0")) }; + var expectedManifestNotUpdated = new ManifestId[] { new ManifestId("test-manifest-3"), new ManifestId("test-manifest-4") }; + + // Write mock manifests + var installedManifestDir = Path.Combine(testDir, "dotnet", "sdk-manifests", featureBand); + var adManifestDir = Path.Combine(testDir, ".dotnet", "sdk-advertising", featureBand); + Directory.CreateDirectory(installedManifestDir); + Directory.CreateDirectory(adManifestDir); + foreach ((var manifestId, var existingVersion, var newVersion) in expectedManifestUpdates) + { + Directory.CreateDirectory(Path.Combine(installedManifestDir, manifestId.ToString())); + File.WriteAllText(Path.Combine(installedManifestDir, manifestId.ToString(), _manifestFileName), GetManifestContent(existingVersion)); + Directory.CreateDirectory(Path.Combine(adManifestDir, manifestId.ToString())); + File.WriteAllText(Path.Combine(adManifestDir, manifestId.ToString(), _manifestFileName), GetManifestContent(newVersion)); + } + foreach (var manifest in expectedManifestNotUpdated) + { + Directory.CreateDirectory(Path.Combine(installedManifestDir, manifest.ToString())); + File.WriteAllText(Path.Combine(installedManifestDir, manifest.ToString(), _manifestFileName), GetManifestContent(new ManifestVersion("5.0.0"))); + Directory.CreateDirectory(Path.Combine(adManifestDir, manifest.ToString())); + File.WriteAllText(Path.Combine(adManifestDir, manifest.ToString(), _manifestFileName), GetManifestContent(new ManifestVersion("5.0.0"))); + } + + var manifestDirs = expectedManifestUpdates.Select(manifest => manifest.Item1) + .Concat(expectedManifestNotUpdated) + .Select(manifest => Path.Combine(installedManifestDir, manifest.ToString(), _manifestFileName)) + .ToArray(); + var workloadManifestProvider = new MockManifestProvider(manifestDirs); + var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot); + var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadManifestProvider, nugetDownloader, testDir); + + var manifestUpdates = manifestUpdater.CalculateManifestUpdates(new SdkFeatureBand(featureBand)); + manifestUpdates.Should().BeEquivalentTo(expectedManifestUpdates); + } + + internal static string GetManifestContent(ManifestVersion version) + { + return $@"{{ + ""version"": {version.ToString().Substring(0, 1)}, + ""workloads"": {{ + }} + }}, + ""packs"": {{ + }} +}}"; + } + } +} diff --git a/src/Tests/dotnet-workload-install.Tests/MockManifestProvider.cs b/src/Tests/dotnet-workload-install.Tests/MockManifestProvider.cs index ca35918539ef..ae369af4ecfb 100644 --- a/src/Tests/dotnet-workload-install.Tests/MockManifestProvider.cs +++ b/src/Tests/dotnet-workload-install.Tests/MockManifestProvider.cs @@ -16,7 +16,13 @@ public MockManifestProvider(params string[] filePaths) _filePaths = filePaths; } - public IEnumerable GetManifestDirectories() => throw new System.NotImplementedException(); + public IEnumerable GetManifestDirectories() + { + foreach (var filePath in _filePaths) + { + yield return Path.GetDirectoryName(filePath); + } + } public IEnumerable<(string manifestId, Stream manifestStream)> GetManifests() { @@ -25,5 +31,5 @@ public MockManifestProvider(params string[] filePaths) yield return (filePath, new FileStream(filePath, FileMode.Open, FileAccess.Read)); } } - } + } } diff --git a/src/Tests/dotnet-workload-install.Tests/MockNuGetPackageInstaller.cs b/src/Tests/dotnet-workload-install.Tests/MockNuGetPackageInstaller.cs index f04efc9de89a..0a330c502ee9 100644 --- a/src/Tests/dotnet-workload-install.Tests/MockNuGetPackageInstaller.cs +++ b/src/Tests/dotnet-workload-install.Tests/MockNuGetPackageInstaller.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; @@ -11,27 +12,30 @@ namespace Microsoft.DotNet.Cli.NuGetPackageDownloader { internal class MockNuGetPackageDownloader : INuGetPackageDownloader { - private readonly string _installPath; + private readonly string _downloadPath; + private readonly bool _manifestDownload; - public List<(PackageId, NuGetVersion)> InstallCallParams = new List<(PackageId, NuGetVersion)>(); + public List<(PackageId, NuGetVersion)> DownloadCallParams = new List<(PackageId, NuGetVersion)>(); - public List InstallCallResult = new List(); + public List DownloadCallResult = new List(); public List<(string, string)> ExtractCallParams = new List<(string, string)>(); - public MockNuGetPackageDownloader(string dotnetRoot) + public MockNuGetPackageDownloader(string dotnetRoot, bool manifestDownload = false) { - _installPath = Path.Combine(dotnetRoot, "metadata", "temp"); - Directory.CreateDirectory(_installPath); + _manifestDownload = manifestDownload; + _downloadPath = Path.Combine(dotnetRoot, "metadata", "temp"); + Directory.CreateDirectory(_downloadPath); } - public Task DownloadPackageAsync(PackageId packageId, NuGetVersion packageVersion, + public Task DownloadPackageAsync(PackageId packageId, + NuGetVersion packageVersion = null, PackageSourceLocation packageSourceLocation = null, bool includePreview = false) { - InstallCallParams.Add((packageId, packageVersion)); - var path = Path.Combine(_installPath, "mock.nupkg"); - InstallCallResult.Add(path); + DownloadCallParams.Add((packageId, packageVersion)); + var path = Path.Combine(_downloadPath, "mock.nupkg"); + DownloadCallResult.Add(path); File.WriteAllText(path, string.Empty); return Task.FromResult(path); } @@ -39,6 +43,10 @@ public Task DownloadPackageAsync(PackageId packageId, NuGetVersion packa public Task> ExtractPackageAsync(string packagePath, string targetFolder) { ExtractCallParams.Add((packagePath, targetFolder)); + if (_manifestDownload) + { + Directory.CreateDirectory(Path.Combine(targetFolder, "data")); + } return Task.FromResult(new List() as IEnumerable); } } diff --git a/src/Tests/dotnet-workload-install.Tests/MockPackWorkloadInstaller.cs b/src/Tests/dotnet-workload-install.Tests/MockPackWorkloadInstaller.cs index 419ef6fc3a45..3aba1c2a2675 100644 --- a/src/Tests/dotnet-workload-install.Tests/MockPackWorkloadInstaller.cs +++ b/src/Tests/dotnet-workload-install.Tests/MockPackWorkloadInstaller.cs @@ -13,6 +13,8 @@ internal class MockPackWorkloadInstaller : IWorkloadPackInstaller { public IList InstalledPacks = new List(); public IList RolledBackPacks = new List(); + public IList<(ManifestId manifestId, ManifestVersion manifestVersion, SdkFeatureBand sdkFeatureBand)> InstalledManifests = + new List<(ManifestId, ManifestVersion, SdkFeatureBand)>(); public bool GarbageCollectionCalled = false; public MockInstallationRecordRepository InstallationRecordRepository; public bool FailingRollback; @@ -57,8 +59,12 @@ public IWorkloadInstallationRecordRepository GetWorkloadInstallationRecordReposi return InstallationRecordRepository; } + public void InstallWorkloadManifest(ManifestId manifestId, ManifestVersion manifestVersion, SdkFeatureBand sdkFeatureBand) + { + InstalledManifests.Add((manifestId, manifestVersion, sdkFeatureBand)); + } + public void DownloadToOfflineCache(IEnumerable manifests) => throw new System.NotImplementedException(); - public void InstallWorkloadManifest(ManifestId manifestId, ManifestVersion manifestVersion, SdkFeatureBand sdkFeatureBand) => throw new System.NotImplementedException(); public IWorkloadInstaller GetWorkloadInstaller() => throw new NotImplementedException(); } @@ -85,8 +91,11 @@ public void DeleteWorkloadInstallationRecord(WorkloadId workloadId, SdkFeatureBa { WorkloadInstallRecord.Remove(workloadId); } + public IEnumerable GetInstalledWorkloads(SdkFeatureBand sdkFeatureBand) + { + return new List(); + } - public IEnumerable GetInstalledWorkloads(SdkFeatureBand sdkFeatureBand) => throw new NotImplementedException(); public IEnumerable GetFeatureBandsWithInstallationRecords() => throw new NotImplementedException(); } } diff --git a/src/Tests/dotnet-workload-install.Tests/MockWorkloadManifestUpdater.cs b/src/Tests/dotnet-workload-install.Tests/MockWorkloadManifestUpdater.cs new file mode 100644 index 000000000000..bc770f18ea1c --- /dev/null +++ b/src/Tests/dotnet-workload-install.Tests/MockWorkloadManifestUpdater.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.DotNet.Workloads.Workload.Install; +using Microsoft.DotNet.Workloads.Workload.Install.InstallRecord; + +namespace Microsoft.DotNet.Cli.Workload.Install.Tests +{ + internal class MockWorkloadManifestUpdater : IWorkloadManifestUpdater + { + public List UpdateAdvertisingManifestsCallParams = new List(); + public List CalculateManifestUpdatesCallParams = new List(); + private IEnumerable<(ManifestId, ManifestVersion, ManifestVersion)> _manifestUpdates; + + public MockWorkloadManifestUpdater(IEnumerable<(ManifestId, ManifestVersion, ManifestVersion)> manifestUpdates = null) + { + _manifestUpdates = manifestUpdates ?? new List<(ManifestId, ManifestVersion, ManifestVersion)>(); + } + + public Task UpdateAdvertisingManifestsAsync(SdkFeatureBand featureBand, bool includePreview) + { + UpdateAdvertisingManifestsCallParams.Add(featureBand); + return Task.CompletedTask; + } + + public IEnumerable<(ManifestId, ManifestVersion, ManifestVersion)> CalculateManifestUpdates(SdkFeatureBand featureBand) + { + CalculateManifestUpdatesCallParams.Add(featureBand); + return _manifestUpdates; + } + } +}