diff --git a/src/Cli/dotnet/Commands/CliCommandStrings.resx b/src/Cli/dotnet/Commands/CliCommandStrings.resx index 74001a13dfe9..d34d4f76f184 100644 --- a/src/Cli/dotnet/Commands/CliCommandStrings.resx +++ b/src/Cli/dotnet/Commands/CliCommandStrings.resx @@ -1,17 +1,17 @@  - @@ -2724,4 +2724,8 @@ Proceed? duration: + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {0} is the workload set version. {Locked="dotnet workload repair"} + diff --git a/src/Cli/dotnet/Commands/Workload/Install/FileBasedInstaller.cs b/src/Cli/dotnet/Commands/Workload/Install/FileBasedInstaller.cs index 99681825c16f..9328fd2f9f32 100644 --- a/src/Cli/dotnet/Commands/Workload/Install/FileBasedInstaller.cs +++ b/src/Cli/dotnet/Commands/Workload/Install/FileBasedInstaller.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using System.Text.Json; +using Microsoft.DotNet.Cli.Commands.Workload; using Microsoft.DotNet.Cli.Commands.Workload.Config; using Microsoft.DotNet.Cli.Commands.Workload.Install.WorkloadInstallRecords; using Microsoft.DotNet.Cli.Extensions; @@ -642,7 +643,7 @@ public IEnumerable GetWorkloadHistoryRecords(string sdkFe public void Shutdown() { // Perform any additional cleanup here that's intended to run at the end of the command, regardless - // of success or failure. For file based installs, there shouldn't be any additional work to + // of success or failure. For file based installs, there shouldn't be any additional work to // perform. } diff --git a/src/Cli/dotnet/Commands/Workload/Install/WorkloadInstallerFactory.cs b/src/Cli/dotnet/Commands/Workload/Install/WorkloadInstallerFactory.cs index 6ad1553257f2..e260bfa90fa7 100644 --- a/src/Cli/dotnet/Commands/Workload/Install/WorkloadInstallerFactory.cs +++ b/src/Cli/dotnet/Commands/Workload/Install/WorkloadInstallerFactory.cs @@ -3,6 +3,7 @@ #nullable disable +using Microsoft.DotNet.Cli.Commands.Workload; using Microsoft.DotNet.Cli.NuGetPackageDownloader; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Configurer; @@ -48,7 +49,7 @@ public static IInstaller GetWorkloadInstaller( userProfileDir ??= CliFolderPathCalculator.DotnetUserProfileFolderPath; - return new FileBasedInstaller( + var installer = new FileBasedInstaller( reporter, sdkFeatureBand, workloadResolver, @@ -59,6 +60,25 @@ public static IInstaller GetWorkloadInstaller( verbosity: verbosity, packageSourceLocation: packageSourceLocation, restoreActionConfig: restoreActionConfig); + + // Attach corruption repairer to recover from corrupt workload sets + if (nugetPackageDownloader is not null && + workloadResolver?.GetWorkloadManifestProvider() is SdkDirectoryWorkloadManifestProvider sdkProvider && + sdkProvider.CorruptionRepairer is null) + { + sdkProvider.CorruptionRepairer = new WorkloadManifestCorruptionRepairer( + reporter, + installer, + workloadResolver, + sdkFeatureBand, + dotnetDir, + userProfileDir, + nugetPackageDownloader, + packageSourceLocation, + verbosity); + } + + return installer; } private static bool CanWriteToDotnetRoot(string dotnetDir = null) diff --git a/src/Cli/dotnet/Commands/Workload/Repair/WorkloadRepairCommand.cs b/src/Cli/dotnet/Commands/Workload/Repair/WorkloadRepairCommand.cs index 46fee7742ebb..7712399f3eee 100644 --- a/src/Cli/dotnet/Commands/Workload/Repair/WorkloadRepairCommand.cs +++ b/src/Cli/dotnet/Commands/Workload/Repair/WorkloadRepairCommand.cs @@ -69,7 +69,8 @@ public override int Execute() { Reporter.WriteLine(); - var workloadIds = _workloadInstaller.GetWorkloadInstallationRecordRepository().GetInstalledWorkloads(new SdkFeatureBand(_sdkVersion)); + var sdkFeatureBand = new SdkFeatureBand(_sdkVersion); + var workloadIds = _workloadInstaller.GetWorkloadInstallationRecordRepository().GetInstalledWorkloads(sdkFeatureBand); if (!workloadIds.Any()) { @@ -79,7 +80,7 @@ public override int Execute() Reporter.WriteLine(string.Format(CliCommandStrings.RepairingWorkloads, string.Join(" ", workloadIds))); - ReinstallWorkloadsBasedOnCurrentManifests(workloadIds, new SdkFeatureBand(_sdkVersion)); + ReinstallWorkloadsBasedOnCurrentManifests(workloadIds, sdkFeatureBand); WorkloadInstallCommand.TryRunGarbageCollection(_workloadInstaller, Reporter, Verbosity, workloadSetVersion => _workloadResolverFactory.CreateForWorkloadSet(_dotnetPath, _sdkVersion.ToString(), _userProfileDir, workloadSetVersion)); @@ -105,4 +106,5 @@ private void ReinstallWorkloadsBasedOnCurrentManifests(IEnumerable w { _workloadInstaller.RepairWorkloads(workloadIds, sdkFeatureBand); } + } diff --git a/src/Cli/dotnet/Commands/Workload/WorkloadHistoryRecorder.cs b/src/Cli/dotnet/Commands/Workload/WorkloadHistoryRecorder.cs index ab64b7539912..0690f5e12347 100644 --- a/src/Cli/dotnet/Commands/Workload/WorkloadHistoryRecorder.cs +++ b/src/Cli/dotnet/Commands/Workload/WorkloadHistoryRecorder.cs @@ -54,6 +54,10 @@ public void Run(Action workloadAction) private WorkloadHistoryState GetWorkloadState() { var resolver = _workloadResolverFunc(); + if (resolver.GetWorkloadManifestProvider() is SdkDirectoryWorkloadManifestProvider sdkProvider) + { + sdkProvider.CorruptionFailureMode = ManifestCorruptionFailureMode.Ignore; + } var currentWorkloadVersion = resolver.GetWorkloadVersion().Version; return new WorkloadHistoryState() { diff --git a/src/Cli/dotnet/Commands/Workload/WorkloadManifestCorruptionRepairer.cs b/src/Cli/dotnet/Commands/Workload/WorkloadManifestCorruptionRepairer.cs new file mode 100644 index 000000000000..28bfe766a5f0 --- /dev/null +++ b/src/Cli/dotnet/Commands/Workload/WorkloadManifestCorruptionRepairer.cs @@ -0,0 +1,121 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.Commands.Workload.Install; +using Microsoft.DotNet.Cli.NuGetPackageDownloader; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.NET.Sdk.WorkloadManifestReader; + +namespace Microsoft.DotNet.Cli.Commands.Workload; + +internal sealed class WorkloadManifestCorruptionRepairer : IWorkloadManifestCorruptionRepairer +{ + private readonly IReporter _reporter; + private readonly IInstaller _workloadInstaller; + private readonly IWorkloadResolver _workloadResolver; + private readonly SdkFeatureBand _sdkFeatureBand; + private readonly string _dotnetPath; + private readonly string _userProfileDir; + private readonly INuGetPackageDownloader? _packageDownloader; + private readonly PackageSourceLocation? _packageSourceLocation; + private readonly VerbosityOptions _verbosity; + + private bool _checked; + + public WorkloadManifestCorruptionRepairer( + IReporter reporter, + IInstaller workloadInstaller, + IWorkloadResolver workloadResolver, + SdkFeatureBand sdkFeatureBand, + string dotnetPath, + string userProfileDir, + INuGetPackageDownloader? packageDownloader, + PackageSourceLocation? packageSourceLocation, + VerbosityOptions verbosity) + { + _reporter = reporter ?? NullReporter.Instance; + _workloadInstaller = workloadInstaller; + _workloadResolver = workloadResolver; + _sdkFeatureBand = sdkFeatureBand; + _dotnetPath = dotnetPath; + _userProfileDir = userProfileDir; + _packageDownloader = packageDownloader; + _packageSourceLocation = packageSourceLocation; + _verbosity = verbosity; + } + + public void EnsureManifestsHealthy(ManifestCorruptionFailureMode failureMode) + { + if (_checked) + { + return; + } + + _checked = true; + + if (failureMode == ManifestCorruptionFailureMode.Ignore) + { + return; + } + + // Get the workload set directly from the provider - it was already resolved during construction + // and doesn't require reading the install state file again + var provider = _workloadResolver.GetWorkloadManifestProvider() as SdkDirectoryWorkloadManifestProvider; + var workloadSet = provider?.ResolvedWorkloadSet; + + if (workloadSet is null) + { + // No workload set is being used + return; + } + + if (!provider?.HasMissingManifests(workloadSet) ?? true) + { + return; + } + + if (failureMode == ManifestCorruptionFailureMode.Throw) + { + throw new InvalidOperationException(string.Format(CliCommandStrings.WorkloadSetHasMissingManifests, workloadSet.Version)); + } + + _reporter.WriteLine($"Repairing workload set {workloadSet.Version}..."); + CliTransaction.RunNew(context => RepairCorruptWorkloadSet(context, workloadSet)); + } + + + + private void RepairCorruptWorkloadSet(ITransactionContext context, WorkloadSet workloadSet) + { + var manifestUpdates = CreateManifestUpdatesFromWorkloadSet(workloadSet); + + foreach (var manifestUpdate in manifestUpdates) + { + _workloadInstaller.InstallWorkloadManifest(manifestUpdate, context); + } + + } + + [MemberNotNull(nameof(_packageDownloader))] + private IEnumerable CreateManifestUpdatesFromWorkloadSet(WorkloadSet workloadSet) + { + if (_packageDownloader is null) + { + throw new InvalidOperationException("Package downloader is required to repair workload manifests."); + } + + var manifestUpdater = new WorkloadManifestUpdater( + _reporter, + _workloadResolver, + _packageDownloader, + _userProfileDir, + _workloadInstaller.GetWorkloadInstallationRecordRepository(), + _workloadInstaller, + _packageSourceLocation, + displayManifestUpdates: _verbosity >= VerbosityOptions.detailed); + + return manifestUpdater.CalculateManifestUpdatesForWorkloadSet(workloadSet); + } +} diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf index 69f0ea0788e1..1512b9be7de3 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf @@ -4074,6 +4074,11 @@ Pokud chcete zobrazit hodnotu, zadejte odpovídající volbu příkazového řá Verze {0} úlohy, která byla zadána v {1}, nebyla nalezena. Spuštěním příkazu dotnet workload restore nainstalujte tuto verzi úlohy. {Locked="dotnet workload restore"} + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {0} is the workload set version. {Locked="dotnet workload repair"} + A workload version to display or one or more workloads and their versions joined by the '@' character. Verze úlohy, která se má zobrazit, nebo jedna nebo více úloh a jejich verze spojené znakem @. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf index e3a05f3c7545..6648f17aedbe 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf @@ -4074,6 +4074,11 @@ Um einen Wert anzuzeigen, geben Sie die entsprechende Befehlszeilenoption an, oh Die Arbeitsauslastungsversion {0}, die in {1} angegeben wurde, wurde nicht gefunden. Führen Sie „dotnet workload restore“ aus, um diese Workloadversion zu installieren. {Locked="dotnet workload restore"} + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {0} is the workload set version. {Locked="dotnet workload repair"} + A workload version to display or one or more workloads and their versions joined by the '@' character. Eine Workloadversion zum Anzeigen oder mindestens eine Workload und deren Versionen, die mit dem Zeichen "@" verknüpft sind. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf index 82aa42c23b79..93d84a383f4e 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf @@ -4074,6 +4074,11 @@ Para mostrar un valor, especifique la opción de línea de comandos correspondie No se encontró la versión de carga de trabajo {0}, que se especificó en {1}. Ejecuta "dotnet workload restore" para instalar esta versión de carga de trabajo. {Locked="dotnet workload restore"} + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {0} is the workload set version. {Locked="dotnet workload repair"} + A workload version to display or one or more workloads and their versions joined by the '@' character. Una versión de carga de trabajo para mostrar o una o varias cargas de trabajo y sus versiones unidas por el carácter "@". diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf index b6da938fbb90..b2524264859d 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf @@ -4074,6 +4074,11 @@ Pour afficher une valeur, spécifiez l’option de ligne de commande corresponda La version de charge de travail {0}, qui a été spécifiée dans {1}, est introuvable. Exécutez « dotnet workload restore » pour installer cette version de charge de travail. {Locked="dotnet workload restore"} + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {0} is the workload set version. {Locked="dotnet workload repair"} + A workload version to display or one or more workloads and their versions joined by the '@' character. Version de charge de travail à afficher ou une ou plusieurs charges de travail et leurs versions jointes par le caractère « @ ». diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf index d8ded9eb3783..512bf4612264 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf @@ -4074,6 +4074,11 @@ Per visualizzare un valore, specifica l'opzione della riga di comando corrispond La versione del carico di lavoro {0}, specificata in {1}, non è stata trovata. Eseguire "dotnet workload restore" per installare questa versione del carico di lavoro. {Locked="dotnet workload restore"} + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {0} is the workload set version. {Locked="dotnet workload repair"} + A workload version to display or one or more workloads and their versions joined by the '@' character. Una versione del carico di lavoro da visualizzare oppure uno o più carichi di lavoro e le relative versioni unite dal carattere '@'. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf index 373d63a6721b..aa044bf2b8c3 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf @@ -4074,6 +4074,11 @@ To display a value, specify the corresponding command-line option without provid {1} で指定されたワークロード バージョン {0} が見つかりませんでした。"dotnet workload restore" を実行して、このワークロード バージョンをインストールします。 {Locked="dotnet workload restore"} + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {0} is the workload set version. {Locked="dotnet workload repair"} + A workload version to display or one or more workloads and their versions joined by the '@' character. 表示するワークロードのバージョン、または '@' 文字で結合された 1 つ以上のワークロードとそのバージョン。 diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf index 6935ae03eb40..7290a2610e18 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf @@ -4074,6 +4074,11 @@ To display a value, specify the corresponding command-line option without provid {1}에 지정된 워크로드 버전 {0}을(를) 찾을 수 없습니다. "dotnet workload restore"을 실행하여 이 워크로드 버전을 설치합니다. {Locked="dotnet workload restore"} + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {0} is the workload set version. {Locked="dotnet workload repair"} + A workload version to display or one or more workloads and their versions joined by the '@' character. 표시할 워크로드 버전 또는 '@' 문자로 결합된 하나 이상의 워크로드와 해당 버전입니다. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf index 952dc2ec304c..5ef1ec80a17e 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf @@ -4074,6 +4074,11 @@ Aby wyświetlić wartość, należy podać odpowiednią opcję wiersza poleceń Nie znaleziono wersji obciążenia {0} określonej w kontenerze {1}. Uruchom polecenie „dotnet workload restore”, aby zainstalować tę wersję obciążenia. {Locked="dotnet workload restore"} + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {0} is the workload set version. {Locked="dotnet workload repair"} + A workload version to display or one or more workloads and their versions joined by the '@' character. Wersja obciążenia do wyświetlenia lub jedno lub więcej obciążeń i ich wersji połączonych znakiem „@”. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf index c0d1afd99daf..0e7a0f908f2b 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf @@ -4074,6 +4074,11 @@ Para exibir um valor, especifique a opção de linha de comando correspondente s A versão da carga de trabalho {0}, especificada em {1}, não foi localizada. Execute "dotnet workload restore" para instalar esta versão da carga de trabalho. {Locked="dotnet workload restore"} + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {0} is the workload set version. {Locked="dotnet workload repair"} + A workload version to display or one or more workloads and their versions joined by the '@' character. Uma versão de carga de trabalho para exibir ou uma ou mais cargas de trabalho e suas versões unidas pelo caractere ''@''. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf index e0611a0d0423..cfb075bb07df 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf @@ -4075,6 +4075,11 @@ To display a value, specify the corresponding command-line option without provid Версия рабочей нагрузки {0}, указанная в {1}, не найдена. Запустите команду "dotnet workload restore", чтобы установить эту версию рабочей нагрузки. {Locked="dotnet workload restore"} + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {0} is the workload set version. {Locked="dotnet workload repair"} + A workload version to display or one or more workloads and their versions joined by the '@' character. Версия рабочей нагрузки для отображения или одной или нескольких рабочих нагрузок и их версий, соединенных символом "@". diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf index 24d365bdb0e0..195553f39fa9 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf @@ -4074,6 +4074,11 @@ Bir değeri görüntülemek için, bir değer sağlamadan ilgili komut satırı {1} konumunda belirtilen {0} iş yükü sürümü bulunamadı. Bu iş yükü sürümünü yüklemek için "dotnet workload restore" komutunu çalıştırın. {Locked="dotnet workload restore"} + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {0} is the workload set version. {Locked="dotnet workload repair"} + A workload version to display or one or more workloads and their versions joined by the '@' character. Görüntülenecek bir iş yükü sürümü veya bir veya daha fazla iş yükü ve bunların '@' karakteriyle birleştirilen sürümleri. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf index f5d749e1d50d..a9bf482bb05e 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf @@ -4074,6 +4074,11 @@ To display a value, specify the corresponding command-line option without provid 找不到在 {1} 中指定的工作负载版本 {0}。运行“dotnet workload restore”以安装此工作负载版本。 {Locked="dotnet workload restore"} + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {0} is the workload set version. {Locked="dotnet workload repair"} + A workload version to display or one or more workloads and their versions joined by the '@' character. 要显示的工作负载版本,或一个或多个工作负载,并且其版本由 ‘@’ 字符联接。 diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf index 93e2b84eaee7..39969f14662e 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf @@ -4074,6 +4074,11 @@ To display a value, specify the corresponding command-line option without provid 找不到 {1} 中指定的工作負載版本 {0}。執行 "dotnet workload restore" 以安裝此工作負載版本。 {Locked="dotnet workload restore"} + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {0} is the workload set version. {Locked="dotnet workload repair"} + A workload version to display or one or more workloads and their versions joined by the '@' character. 要顯示的工作負載版本,或是一或多個工作負載及其由 '@' 字元連接的版本。 diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/IWorkloadManifestCorruptionRepairer.cs b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/IWorkloadManifestCorruptionRepairer.cs new file mode 100644 index 000000000000..4c3a9e907ba9 --- /dev/null +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/IWorkloadManifestCorruptionRepairer.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.NET.Sdk.WorkloadManifestReader +{ + /// + /// Provides a hook for the CLI layer to detect and repair corrupt workload manifest installations + /// before the manifests are loaded by the resolver. + /// + public interface IWorkloadManifestCorruptionRepairer + { + /// + /// Ensures that the manifests required by the current resolver are present and healthy. + /// + /// How to handle corruption if detected. + void EnsureManifestsHealthy(ManifestCorruptionFailureMode failureMode); + } +} diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/IWorkloadManifestProvider.cs b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/IWorkloadManifestProvider.cs index 4a2df9d5ecaa..887b170fcd04 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/IWorkloadManifestProvider.cs +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/IWorkloadManifestProvider.cs @@ -3,6 +3,30 @@ namespace Microsoft.NET.Sdk.WorkloadManifestReader { + /// + /// Specifies how the manifest provider should handle corrupt or missing workload manifests. + /// + public enum ManifestCorruptionFailureMode + { + /// + /// Attempt to repair using the CorruptionRepairer if available, otherwise throw. + /// This is the default mode for commands that modify workloads. + /// + Repair, + + /// + /// Throw a helpful error message suggesting how to fix the issue. + /// Use this for read-only/info commands. + /// + Throw, + + /// + /// Silently ignore missing manifests and continue. + /// Use this for history recording or other scenarios where missing manifests are acceptable. + /// + Ignore + } + /// /// This abstracts out the process of locating and loading a set of manifests to be loaded into a /// workload manifest resolver and resolved into a single coherent model. diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/SdkDirectoryWorkloadManifestProvider.cs b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/SdkDirectoryWorkloadManifestProvider.cs index c3da2743abc3..7329564dd6bf 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/SdkDirectoryWorkloadManifestProvider.cs +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/SdkDirectoryWorkloadManifestProvider.cs @@ -6,6 +6,7 @@ using Microsoft.DotNet.Cli; using Microsoft.DotNet.Cli.Commands.Workload; using Microsoft.NET.Sdk.Localization; +using Microsoft.DotNet.Cli.Commands; using static Microsoft.NET.Sdk.WorkloadManifestReader.IWorkloadManifestProvider; namespace Microsoft.NET.Sdk.WorkloadManifestReader @@ -31,6 +32,24 @@ public partial class SdkDirectoryWorkloadManifestProvider : IWorkloadManifestPro private bool _useManifestsFromInstallState = true; private bool? _globalJsonSpecifiedWorkloadSets = null; + /// + /// Optional hook that allows the CLI to ensure workload manifests are available (and repaired if necessary) + /// before this provider attempts to enumerate them. + /// + public IWorkloadManifestCorruptionRepairer? CorruptionRepairer { get; set; } + + /// + /// Specifies how this provider should handle corrupt or missing workload manifests. + /// Default is . + /// + public ManifestCorruptionFailureMode CorruptionFailureMode { get; set; } = ManifestCorruptionFailureMode.Repair; + + /// + /// Gets the resolved workload set, if any. This is populated during construction/refresh + /// and does not trigger corruption checking. + /// + public WorkloadSet? ResolvedWorkloadSet => _workloadSet; + // This will be non-null if there is an error loading manifests that should be thrown when they need to be accessed. // We delay throwing the error so that in the case where global.json specifies a workload set that isn't installed, // we can successfully construct a resolver and install that workload set @@ -247,6 +266,19 @@ void ThrowExceptionIfManifestsNotAvailable() public WorkloadVersionInfo GetWorkloadVersion() { + if (CorruptionRepairer != null) + { + CorruptionRepairer.EnsureManifestsHealthy(CorruptionFailureMode); + } + else if (_workloadSet != null && CorruptionFailureMode != ManifestCorruptionFailureMode.Ignore) + { + // No repairer attached - check for missing manifests and throw a helpful error + if (HasMissingManifests(_workloadSet)) + { + throw new InvalidOperationException(string.Format(Strings.WorkloadSetHasMissingManifests, _workloadSet.Version)); + } + } + if (_globalJsonWorkloadSetVersion != null) { // _exceptionToThrow is set to null here if and only if the workload set is not installed. @@ -290,6 +322,18 @@ public WorkloadVersionInfo GetWorkloadVersion() public IEnumerable GetManifests() { + if (CorruptionRepairer != null) + { + CorruptionRepairer.EnsureManifestsHealthy(CorruptionFailureMode); + } + else if (_workloadSet != null && CorruptionFailureMode != ManifestCorruptionFailureMode.Ignore) + { + // No repairer attached - check for missing manifests and throw a helpful error + if (HasMissingManifests(_workloadSet)) + { + throw new InvalidOperationException(string.Format(Strings.WorkloadSetHasMissingManifests, _workloadSet.Version)); + } + } ThrowExceptionIfManifestsNotAvailable(); // Scan manifest directories @@ -363,6 +407,10 @@ void ProbeDirectory(string manifestDirectory, string featureBand) var manifestDirectory = GetManifestDirectoryFromSpecifier(manifestSpecifier); if (manifestDirectory == null) { + if (CorruptionFailureMode == ManifestCorruptionFailureMode.Ignore) + { + continue; + } throw new FileNotFoundException(string.Format(Strings.ManifestFromWorkloadSetNotFound, manifestSpecifier.ToString(), _workloadSet.Version)); } AddManifest(manifestSpecifier.Id.ToString(), manifestDirectory, manifestSpecifier.FeatureBand.ToString(), kvp.Value.Version.ToString()); @@ -380,6 +428,10 @@ void ProbeDirectory(string manifestDirectory, string featureBand) var manifestDirectory = GetManifestDirectoryFromSpecifier(manifestSpecifier); if (manifestDirectory == null) { + if (CorruptionFailureMode == ManifestCorruptionFailureMode.Ignore) + { + continue; + } throw new FileNotFoundException(string.Format(Strings.ManifestFromInstallStateNotFound, manifestSpecifier.ToString(), _installStateFilePath)); } AddManifest(manifestSpecifier.Id.ToString(), manifestDirectory, manifestSpecifier.FeatureBand.ToString(), kvp.Value.Version.ToString()); @@ -510,6 +562,23 @@ void ProbeDirectory(string manifestDirectory, string featureBand) return null; } + /// + /// Checks if the workload set has any manifests that are missing from disk. + /// This checks all manifest roots (including user-local installs). + /// + public bool HasMissingManifests(WorkloadSet workloadSet) + { + foreach (var manifestEntry in workloadSet.ManifestVersions) + { + var manifestSpecifier = new ManifestSpecifier(manifestEntry.Key, manifestEntry.Value.Version, manifestEntry.Value.FeatureBand); + if (GetManifestDirectoryFromSpecifier(manifestSpecifier) == null) + { + return true; + } + } + return false; + } + /// /// Returns installed workload sets that are available for this SDK (ie are in the same feature band) /// @@ -538,7 +607,7 @@ Dictionary GetAvailableWorkloadSetsInternal(SdkFeatureBand? } else { - // Get workload sets for all feature bands + // Get workload sets for all feature bands foreach (var featureBandDirectory in Directory.GetDirectories(manifestRoot)) { AddWorkloadSetsForFeatureBand(availableWorkloadSets, featureBandDirectory); diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/Strings.resx b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/Strings.resx index cf418daa75cb..e583daad115c 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/Strings.resx +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/Strings.resx @@ -1,17 +1,17 @@  - @@ -213,5 +213,9 @@ No manifest with ID {0} exists. + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {Locked="dotnet workload repair"} + diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.cs.xlf b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.cs.xlf index 1891cc4ff30b..e52c35088d8a 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.cs.xlf +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.cs.xlf @@ -142,6 +142,11 @@ Nevyřešený cíl {0} pro přesměrování úlohy {1} v manifestu {2} [{3}] + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {Locked="dotnet workload repair"} + Workload version {0}, which was specified in {1}, was not found. Run "dotnet workload restore" to install this workload version. Verze {0} úlohy, která byla zadána v {1}, nebyla nalezena. Spuštěním příkazu dotnet workload restore nainstalujte tuto verzi úlohy. diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.de.xlf b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.de.xlf index aa35c3838c22..c1b3de9e9b04 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.de.xlf +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.de.xlf @@ -142,6 +142,11 @@ Nicht aufgelöstes Ziel „{0}“ für die Workloadumleitung „{1}“ im Manifest „{2}“ [{3}] + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {Locked="dotnet workload repair"} + Workload version {0}, which was specified in {1}, was not found. Run "dotnet workload restore" to install this workload version. Die Arbeitsauslastungsversion {0}, die in {1} angegeben wurde, wurde nicht gefunden. Führen Sie „dotnet workload restore“ aus, um diese Workloadversion zu installieren. diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.es.xlf b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.es.xlf index 485e6e0a0cf8..91127d3307bf 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.es.xlf +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.es.xlf @@ -142,6 +142,11 @@ Destino sin resolver '{0}' para redirección de carga de trabajo '{1}' en el manifiesto '{2}' [{3}] + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {Locked="dotnet workload repair"} + Workload version {0}, which was specified in {1}, was not found. Run "dotnet workload restore" to install this workload version. No se encontró la versión de carga de trabajo {0}, que se especificó en {1}. Ejecuta "dotnet workload restore" para instalar esta versión de carga de trabajo. diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.fr.xlf b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.fr.xlf index 0ae7f6800bd3..550221d36e00 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.fr.xlf +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.fr.xlf @@ -142,6 +142,11 @@ Cible non résolue « {0} » pour la redirection de charge de travail « {1} » dans le manifeste « {2} » [{3}] + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {Locked="dotnet workload repair"} + Workload version {0}, which was specified in {1}, was not found. Run "dotnet workload restore" to install this workload version. La version de charge de travail {0}, qui a été spécifiée dans {1}, est introuvable. Exécutez « dotnet workload restore » pour installer cette version de charge de travail. diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.it.xlf b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.it.xlf index 7c666799e412..b2124effaba4 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.it.xlf +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.it.xlf @@ -142,6 +142,11 @@ Destinazione non risolta '{0}' per il reindirizzamento del carico di lavoro '{1}' nel manifesto '{2}' [{3}] + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {Locked="dotnet workload repair"} + Workload version {0}, which was specified in {1}, was not found. Run "dotnet workload restore" to install this workload version. La versione del carico di lavoro {0}, specificata in {1}, non è stata trovata. Eseguire "dotnet workload restore" per installare questa versione del carico di lavoro. diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.ja.xlf b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.ja.xlf index cc172807eaa9..5254999963fa 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.ja.xlf +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.ja.xlf @@ -142,6 +142,11 @@ マニフェスト '{2}' [{3}] 内のワークロード リダイレクト '{1}' に対する未解決のターゲット '{0}' + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {Locked="dotnet workload repair"} + Workload version {0}, which was specified in {1}, was not found. Run "dotnet workload restore" to install this workload version. {1} で指定されたワークロード バージョン {0} が見つかりませんでした。"dotnet workload restore" を実行して、このワークロード バージョンをインストールします。 diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.ko.xlf b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.ko.xlf index 385531b1c48b..06a3d8b1e20b 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.ko.xlf +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.ko.xlf @@ -142,6 +142,11 @@ 매니페스트 '{2}' [{3}]의 워크로드 리디렉션 '{1}'에 대한 확인되지 않는 대상 '{0}' + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {Locked="dotnet workload repair"} + Workload version {0}, which was specified in {1}, was not found. Run "dotnet workload restore" to install this workload version. {1}에 지정된 워크로드 버전 {0}을(를) 찾을 수 없습니다. "dotnet workload restore"을 실행하여 이 워크로드 버전을 설치합니다. diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.pl.xlf b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.pl.xlf index 2f5dc549ddf0..2fb7cda88a37 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.pl.xlf +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.pl.xlf @@ -142,6 +142,11 @@ Nierozpoznany element docelowy „{0}” przekierowania obciążenia „{1}” w manifeście „{2}” [{3}] + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {Locked="dotnet workload repair"} + Workload version {0}, which was specified in {1}, was not found. Run "dotnet workload restore" to install this workload version. Nie znaleziono wersji obciążenia {0} określonej w kontenerze {1}. Uruchom polecenie „dotnet workload restore”, aby zainstalować tę wersję obciążenia. diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.pt-BR.xlf b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.pt-BR.xlf index f5f0331a25ec..19aa8c2b6001 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.pt-BR.xlf +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.pt-BR.xlf @@ -142,6 +142,11 @@ Destino '{0}' não resolvido para o redirecionamento de carga de trabalho '{1}' no manifesto '{2}' [{3}] + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {Locked="dotnet workload repair"} + Workload version {0}, which was specified in {1}, was not found. Run "dotnet workload restore" to install this workload version. A versão da carga de trabalho {0}, especificada em {1}, não foi localizada. Execute "dotnet workload restore" para instalar esta versão da carga de trabalho. diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.ru.xlf b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.ru.xlf index bf88a6af7bbe..01bf7048e135 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.ru.xlf +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.ru.xlf @@ -142,6 +142,11 @@ Неразрешенный целевой объект "{0}" для перенаправления рабочей нагрузки "{1}" в манифесте "{2}" [{3}] + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {Locked="dotnet workload repair"} + Workload version {0}, which was specified in {1}, was not found. Run "dotnet workload restore" to install this workload version. Версия рабочей нагрузки {0}, указанная в {1}, не найдена. Запустите команду "dotnet workload restore", чтобы установить эту версию рабочей нагрузки. diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.tr.xlf b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.tr.xlf index 786f001a3435..15e1e6425967 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.tr.xlf +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.tr.xlf @@ -142,6 +142,11 @@ '{2}' [{3}] bildirimindeki '{1}' iş akışı yeniden yönlendirmesi için '{0}' hedefi çözümlenemedi + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {Locked="dotnet workload repair"} + Workload version {0}, which was specified in {1}, was not found. Run "dotnet workload restore" to install this workload version. {1} konumunda belirtilen {0} iş yükü sürümü bulunamadı. Bu iş yükü sürümünü yüklemek için "dotnet workload restore" komutunu çalıştırın. diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.zh-Hans.xlf b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.zh-Hans.xlf index 911a2731d284..5770b450720b 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.zh-Hans.xlf +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.zh-Hans.xlf @@ -142,6 +142,11 @@ 工作负载未解析的目标“{0}”重定向到清单“{2}”[{3}] 中的“{1}” + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {Locked="dotnet workload repair"} + Workload version {0}, which was specified in {1}, was not found. Run "dotnet workload restore" to install this workload version. 找不到在 {1} 中指定的工作负载版本 {0}。运行“dotnet workload restore”以安装此工作负载版本。 diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.zh-Hant.xlf b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.zh-Hant.xlf index 423e6216ecca..2435cecf1b39 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.zh-Hant.xlf +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/xlf/Strings.zh-Hant.xlf @@ -142,6 +142,11 @@ 資訊清單 '{2}' [{3}] 中工作負載重新導向 '{1}' 的未解析目標 '{0}' + + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + Workload set version {0} has missing manifests likely removed by package management. Run "dotnet workload repair" to fix this. + {Locked="dotnet workload repair"} + Workload version {0}, which was specified in {1}, was not found. Run "dotnet workload restore" to install this workload version. 找不到 {1} 中指定的工作負載版本 {0}。執行 "dotnet workload restore" 以安裝此工作負載版本。 diff --git a/test/Microsoft.NET.Sdk.WorkloadManifestReader.Tests/SdkDirectoryWorkloadManifestProviderTests.cs b/test/Microsoft.NET.Sdk.WorkloadManifestReader.Tests/SdkDirectoryWorkloadManifestProviderTests.cs index cbb52e8040c1..7ad32c244356 100644 --- a/test/Microsoft.NET.Sdk.WorkloadManifestReader.Tests/SdkDirectoryWorkloadManifestProviderTests.cs +++ b/test/Microsoft.NET.Sdk.WorkloadManifestReader.Tests/SdkDirectoryWorkloadManifestProviderTests.cs @@ -436,7 +436,7 @@ public void ItThrowsIfManifestFromWorkloadSetIsNotFound() var sdkDirectoryWorkloadManifestProvider = new SdkDirectoryWorkloadManifestProvider(sdkRootPath: _fakeDotnetRootDirectory, sdkVersion: "8.0.200", userProfileDir: null, globalJsonPath: null); - Assert.Throws(() => GetManifestContents(sdkDirectoryWorkloadManifestProvider).ToList()); + Assert.Throws(() => GetManifestContents(sdkDirectoryWorkloadManifestProvider).ToList()); } [Fact] @@ -710,9 +710,9 @@ public void ItFailsIfManifestFromWorkloadSetFromInstallStateIsNotInstalled() var sdkDirectoryWorkloadManifestProvider = new SdkDirectoryWorkloadManifestProvider(sdkRootPath: _fakeDotnetRootDirectory, sdkVersion: "8.0.200", userProfileDir: null, globalJsonPath: null); - var ex = Assert.Throws(() => sdkDirectoryWorkloadManifestProvider.GetManifests().ToList()); + var ex = Assert.Throws(() => sdkDirectoryWorkloadManifestProvider.GetManifests().ToList()); - ex.Message.Should().Be(string.Format(Strings.ManifestFromWorkloadSetNotFound, "ios: 11.0.2/8.0.100", "8.0.201")); + ex.Message.Should().Be(string.Format(Strings.WorkloadSetHasMissingManifests, "8.0.201")); } [Fact] diff --git a/test/dotnet.Tests/CommandTests/Workload/Install/CorruptWorkloadSetTestHelper.cs b/test/dotnet.Tests/CommandTests/Workload/Install/CorruptWorkloadSetTestHelper.cs new file mode 100644 index 000000000000..e42c4ea223cd --- /dev/null +++ b/test/dotnet.Tests/CommandTests/Workload/Install/CorruptWorkloadSetTestHelper.cs @@ -0,0 +1,114 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using ManifestReaderTests; +using Microsoft.DotNet.Cli.Commands.Workload; +using Microsoft.DotNet.Cli.Commands.Workload.Install; +using Microsoft.DotNet.Cli.NuGetPackageDownloader; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.NET.Sdk.WorkloadManifestReader; + +namespace Microsoft.DotNet.Cli.Workload.Install.Tests +{ + /// + /// Test helper for setting up corrupt workload set scenarios. + /// + internal static class CorruptWorkloadSetTestHelper + { + /// + /// Sets up a corrupt workload set scenario where manifests are missing but workload set is configured. + /// This simulates package managers deleting manifests during SDK updates. + /// Returns a real SdkDirectoryWorkloadManifestProvider so the corruption repairer can be attached. + /// + public static (string dotnetRoot, string userProfileDir, MockPackWorkloadInstaller mockInstaller, IWorkloadResolver workloadResolver, SdkDirectoryWorkloadManifestProvider manifestProvider) + SetupCorruptWorkloadSet( + TestAssetsManager testAssetsManager, + bool userLocal, + out string sdkFeatureVersion) + { + var testDirectory = testAssetsManager.CreateTestDirectory(identifier: userLocal ? "userlocal" : "default").Path; + var dotnetRoot = Path.Combine(testDirectory, "dotnet"); + var userProfileDir = Path.Combine(testDirectory, "user-profile"); + sdkFeatureVersion = "6.0.100"; + var workloadSetVersion = "6.0.100"; + + // Create workload set contents JSON for the current (corrupt) version + var workloadSetJson = """ +{ + "xamarin-android-build": "8.4.7/6.0.100", + "xamarin-ios-sdk": "10.0.1/6.0.100" +} +"""; + + // Create workload set contents for the updated version + var workloadSetJsonUpdated = """ +{ + "xamarin-android-build": "8.4.8/6.0.100", + "xamarin-ios-sdk": "10.0.2/6.0.100" +} +"""; + + // Create workload set contents for the mock installer + var workloadSetContents = new Dictionary + { + [workloadSetVersion] = workloadSetJson, + ["6.0.101"] = workloadSetJsonUpdated + }; + + // Set up mock installer with workload set support + // Note: Don't pre-populate installedWorkloads - the test focuses on manifest repair, not workload installation + var mockInstaller = new MockPackWorkloadInstaller( + dotnetDir: dotnetRoot, + installedWorkloads: new List(), + workloadSetContents: workloadSetContents); + + string installRoot = userLocal ? userProfileDir : dotnetRoot; + if (userLocal) + { + WorkloadFileBasedInstall.SetUserLocal(dotnetRoot, sdkFeatureVersion); + } + + // Create install state with workload set version + var installStateDir = Path.Combine(installRoot, "metadata", "workloads", RuntimeInformation.ProcessArchitecture.ToString(), sdkFeatureVersion, "InstallState"); + Directory.CreateDirectory(installStateDir); + var installStatePath = Path.Combine(installStateDir, "default.json"); + var installState = new InstallStateContents + { + UseWorkloadSets = true, + WorkloadVersion = workloadSetVersion, + Manifests = new Dictionary + { + ["xamarin-android-build"] = "8.4.7", + ["xamarin-ios-sdk"] = "10.0.1" + } + }; + File.WriteAllText(installStatePath, installState.ToString()); + + // Create workload set folder so the real provider can find it + var workloadSetsRoot = Path.Combine(dotnetRoot, "sdk-manifests", sdkFeatureVersion, "workloadsets", workloadSetVersion); + Directory.CreateDirectory(workloadSetsRoot); + File.WriteAllText(Path.Combine(workloadSetsRoot, "workloadset.workloadset.json"), workloadSetJson); + + // Create mock manifest directories but WITHOUT manifest files to simulate ruined install + var manifestRoot = Path.Combine(dotnetRoot, "sdk-manifests", sdkFeatureVersion); + var androidManifestDir = Path.Combine(manifestRoot, "xamarin-android-build", "8.4.7"); + var iosManifestDir = Path.Combine(manifestRoot, "xamarin-ios-sdk", "10.0.1"); + Directory.CreateDirectory(androidManifestDir); + Directory.CreateDirectory(iosManifestDir); + + // Verify manifests don't exist (simulating the ruined install) + if (File.Exists(Path.Combine(androidManifestDir, "WorkloadManifest.json")) || + File.Exists(Path.Combine(iosManifestDir, "WorkloadManifest.json"))) + { + throw new InvalidOperationException("Test setup failed: manifest files should not exist"); + } + + // Create a real SdkDirectoryWorkloadManifestProvider + var manifestProvider = new SdkDirectoryWorkloadManifestProvider(dotnetRoot, sdkFeatureVersion, userProfileDir, globalJsonPath: null); + var workloadResolver = WorkloadResolver.Create(manifestProvider, dotnetRoot, sdkFeatureVersion, userProfileDir); + mockInstaller.WorkloadResolver = workloadResolver; + + return (dotnetRoot, userProfileDir, mockInstaller, workloadResolver, manifestProvider); + } + } +} diff --git a/test/dotnet.Tests/CommandTests/Workload/Install/MockPackWorkloadInstaller.cs b/test/dotnet.Tests/CommandTests/Workload/Install/MockPackWorkloadInstaller.cs index c1e7354d9d21..9c7bed6b7e9a 100644 --- a/test/dotnet.Tests/CommandTests/Workload/Install/MockPackWorkloadInstaller.cs +++ b/test/dotnet.Tests/CommandTests/Workload/Install/MockPackWorkloadInstaller.cs @@ -139,7 +139,11 @@ public IEnumerable GetWorkloadHistoryRecords(string sdkFe return HistoryRecords; } - public void RepairWorkloads(IEnumerable workloadIds, SdkFeatureBand sdkFeatureBand, DirectoryPath? offlineCache = null) => throw new NotImplementedException(); + public void RepairWorkloads(IEnumerable workloadIds, SdkFeatureBand sdkFeatureBand, DirectoryPath? offlineCache = null) + { + // Repair is essentially a reinstall of existing workloads + CliTransaction.RunNew(context => InstallWorkloads(workloadIds, sdkFeatureBand, context, offlineCache)); + } public void GarbageCollect(Func getResolverForWorkloadSet, DirectoryPath? offlineCache = null, bool cleanAllPacks = false) { @@ -160,6 +164,28 @@ public IWorkloadInstallationRecordRepository GetWorkloadInstallationRecordReposi public void InstallWorkloadManifest(ManifestVersionUpdate manifestUpdate, ITransactionContext transactionContext, DirectoryPath? offlineCache = null) { InstalledManifests.Add((manifestUpdate, offlineCache)); + + // Also create the actual manifest file on disk so that SdkDirectoryWorkloadManifestProvider can find it + if (_dotnetDir != null) + { + var manifestDir = Path.Combine(_dotnetDir, "sdk-manifests", manifestUpdate.NewFeatureBand, + manifestUpdate.ManifestId.ToString(), manifestUpdate.NewVersion.ToString()); + Directory.CreateDirectory(manifestDir); + + var manifestPath = Path.Combine(manifestDir, "WorkloadManifest.json"); + if (!File.Exists(manifestPath)) + { + // Write a minimal manifest file + string manifestContents = $$""" +{ + "version": "{{manifestUpdate.NewVersion}}", + "workloads": {}, + "packs": {} +} +"""; + File.WriteAllText(manifestPath, manifestContents); + } + } } public IEnumerable GetDownloads(IEnumerable workloadIds, SdkFeatureBand sdkFeatureBand, bool includeInstalledItems) diff --git a/test/dotnet.Tests/CommandTests/Workload/Repair/GivenDotnetWorkloadRepair.cs b/test/dotnet.Tests/CommandTests/Workload/Repair/GivenDotnetWorkloadRepair.cs index 4dc46351eae2..6d6d89fb7149 100644 --- a/test/dotnet.Tests/CommandTests/Workload/Repair/GivenDotnetWorkloadRepair.cs +++ b/test/dotnet.Tests/CommandTests/Workload/Repair/GivenDotnetWorkloadRepair.cs @@ -1,17 +1,18 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable - using System.CommandLine; using System.Text.Json; using ManifestReaderTests; using Microsoft.DotNet.Cli.Commands; +using Microsoft.DotNet.Cli.Commands.Workload; using Microsoft.DotNet.Cli.Commands.Workload.Install; using Microsoft.DotNet.Cli.Commands.Workload.Repair; using Microsoft.DotNet.Cli.NuGetPackageDownloader; +using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Workload.Install.Tests; using Microsoft.NET.Sdk.WorkloadManifestReader; +using static Microsoft.NET.Sdk.WorkloadManifestReader.IWorkloadManifestProvider; namespace Microsoft.DotNet.Cli.Workload.Repair.Tests { @@ -85,7 +86,7 @@ public void GivenExtraPacksInstalledRepairGarbageCollects(bool userLocal) // Add extra pack dirs and records var extraPackRecordPath = Path.Combine(installRoot, "metadata", "workloads", "InstalledPacks", "v1", "Test.Pack.A", "1.0.0", sdkFeatureVersion); - Directory.CreateDirectory(Path.GetDirectoryName(extraPackRecordPath)); + Directory.CreateDirectory(Path.GetDirectoryName(extraPackRecordPath)!); var extraPackPath = Path.Combine(installRoot, "packs", "Test.Pack.A", "1.0.0"); Directory.CreateDirectory(extraPackPath); var packRecordContents = JsonSerializer.Serialize(new(new WorkloadPackId("Test.Pack.A"), "1.0.0", WorkloadPackKind.Sdk, extraPackPath, "Test.Pack.A")); @@ -151,5 +152,43 @@ public void GivenMissingPacksRepairFixesInstall(bool userLocal) Directory.GetDirectories(Path.Combine(installRoot, "packs")).Length.Should().Be(7); Directory.GetDirectories(Path.Combine(installRoot, "metadata", "workloads", "InstalledPacks", "v1")).Length.Should().Be(8); } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void GivenMissingManifestsInWorkloadSetModeRepairReinstallsManifests(bool userLocal) + { + var (dotnetRoot, userProfileDir, mockInstaller, workloadResolver, manifestProvider) = + CorruptWorkloadSetTestHelper.SetupCorruptWorkloadSet(_testAssetsManager, userLocal, out string sdkFeatureVersion); + + mockInstaller.InstalledManifests.Should().HaveCount(0); + + var workloadResolverFactory = new MockWorkloadResolverFactory(dotnetRoot, sdkFeatureVersion, workloadResolver, userProfileDir); + + // Attach the corruption repairer to the manifest provider + var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot, manifestDownload: true); + var corruptionRepairer = new WorkloadManifestCorruptionRepairer( + _reporter, + mockInstaller, + workloadResolver, + new SdkFeatureBand(sdkFeatureVersion), + dotnetRoot, + userProfileDir, + nugetDownloader, + packageSourceLocation: null, + VerbosityOptions.detailed); + manifestProvider.CorruptionRepairer = corruptionRepairer; + + // Directly trigger the manifest health check and repair + corruptionRepairer.EnsureManifestsHealthy(ManifestCorruptionFailureMode.Repair); + + // Verify that manifests were installed by the corruption repairer + mockInstaller.InstalledManifests.Should().HaveCount(2, "Manifests should be installed after EnsureManifestsHealthy call"); + mockInstaller.InstalledManifests.Should().Contain(m => m.manifestUpdate.ManifestId.ToString() == "xamarin-android-build"); + mockInstaller.InstalledManifests.Should().Contain(m => m.manifestUpdate.ManifestId.ToString() == "xamarin-ios-sdk"); + + // Verify the repair process was triggered (the corruption repairer shows this message) + _reporter.Lines.Should().Contain(line => line.Contains("Repairing workload set")); + } } } diff --git a/test/dotnet.Tests/CommandTests/Workload/Restore/GivenDotnetWorkloadRestore.cs b/test/dotnet.Tests/CommandTests/Workload/Restore/GivenDotnetWorkloadRestore.cs index 301293b0141f..76b0c8453e00 100644 --- a/test/dotnet.Tests/CommandTests/Workload/Restore/GivenDotnetWorkloadRestore.cs +++ b/test/dotnet.Tests/CommandTests/Workload/Restore/GivenDotnetWorkloadRestore.cs @@ -15,7 +15,7 @@ public GivenDotnetWorkloadRestore(ITestOutputHelper log) : base(log) [Fact] public void ProjectsThatDoNotSupportWorkloadsAreNotInspected() { - if(IsRunningInContainer()) + if (IsRunningInContainer()) { // Skipping test in a Helix container environment due to read-only DOTNET_ROOT, which causes workload restore to fail when writing workload metadata. return; @@ -38,7 +38,7 @@ public void ProjectsThatDoNotSupportWorkloadsAreNotInspected() [Fact] public void ProjectsThatDoNotSupportWorkloadsAndAreTransitivelyReferencedDoNotBreakTheBuild() { - if(IsRunningInContainer()) + if (IsRunningInContainer()) { // Skipping test in a Helix container environment due to read-only DOTNET_ROOT, which causes workload restore to fail when writing workload metadata. return; diff --git a/test/dotnet.Tests/CommandTests/Workload/Search/MockWorkloadResolver.cs b/test/dotnet.Tests/CommandTests/Workload/Search/MockWorkloadResolver.cs index ca5c48b55f71..52f0dc4f56cd 100644 --- a/test/dotnet.Tests/CommandTests/Workload/Search/MockWorkloadResolver.cs +++ b/test/dotnet.Tests/CommandTests/Workload/Search/MockWorkloadResolver.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Reflection.Metadata.Ecma335; using Microsoft.NET.Sdk.WorkloadManifestReader; namespace Microsoft.DotNet.Cli.Workload.Search.Tests @@ -14,19 +15,22 @@ public class MockWorkloadResolver : IWorkloadResolver private readonly Func> _getPacksInWorkload; private readonly Func _getPackInfo; private readonly Func _getManifest; + private readonly IWorkloadManifestProvider _manifestProvider; public MockWorkloadResolver( IEnumerable availableWorkloads, IEnumerable installedManifests = null, Func> getPacks = null, Func getPackInfo = null, - Func getManifest = null) + Func getManifest = null, + IWorkloadManifestProvider manifestProvider = null) { _availableWorkloads = availableWorkloads; _installedManifests = installedManifests; _getPacksInWorkload = getPacks; _getPackInfo = getPackInfo; _getManifest = getManifest; + _manifestProvider = manifestProvider; } public IEnumerable GetAvailableWorkloads() => _availableWorkloads; @@ -48,6 +52,6 @@ public void RefreshWorkloadManifests() { /* noop */ } public IEnumerable GetUpdatedWorkloads(WorkloadResolver advertisingManifestResolver, IEnumerable installedWorkloads) => throw new NotImplementedException(); WorkloadResolver IWorkloadResolver.CreateOverlayResolver(IWorkloadManifestProvider overlayManifestProvider) => throw new NotImplementedException(); WorkloadManifest IWorkloadResolver.GetManifestFromWorkload(WorkloadId workloadId) => _getManifest?.Invoke(workloadId) ?? throw new NotImplementedException(); - public IWorkloadManifestProvider GetWorkloadManifestProvider() => throw new NotImplementedException(); + public IWorkloadManifestProvider GetWorkloadManifestProvider() => _manifestProvider ?? throw new NotImplementedException(); } } diff --git a/test/dotnet.Tests/CommandTests/Workload/Update/GivenDotnetWorkloadUpdate.cs b/test/dotnet.Tests/CommandTests/Workload/Update/GivenDotnetWorkloadUpdate.cs index 3f0a820e801d..ad9d1f77b290 100644 --- a/test/dotnet.Tests/CommandTests/Workload/Update/GivenDotnetWorkloadUpdate.cs +++ b/test/dotnet.Tests/CommandTests/Workload/Update/GivenDotnetWorkloadUpdate.cs @@ -67,13 +67,15 @@ public void GivenWorkloadUpdateFromHistory() IEnumerable installedManifests = new List() { new WorkloadManifestInfo("microsoft.net.sdk.android", "34.0.0-rc.1", "androidDirectory", "8.0.100-rc.1"), new WorkloadManifestInfo("microsoft.net.sdk.ios", "16.4.8825", "iosDirectory", "8.0.100-rc.1") }; + var manifestProvider = new MockManifestProvider(Array.Empty()) { SdkFeatureBand = new SdkFeatureBand("8.0.100-rc.1") }; var workloadResolver = new MockWorkloadResolver( new string[] { "maui-android", "maui-ios" }.Select(s => new WorkloadInfo(new WorkloadId(s), null)), installedManifests, id => new List() { new WorkloadPackId(id.ToString() + "-pack") }, id => id.ToString().Contains("android") ? mauiAndroidPack : - id.ToString().Contains("ios") ? mauiIosPack : null); + id.ToString().Contains("ios") ? mauiIosPack : null, + manifestProvider: manifestProvider); IWorkloadResolverFactory mockResolverFactory = new MockWorkloadResolverFactory( Path.Combine(Path.GetTempPath(), "dotnetTestPath"), @@ -304,7 +306,8 @@ public void UpdateViaWorkloadSet(bool upgrade, bool? installStateUseWorkloadSet, } "; var nugetPackageDownloader = new MockNuGetPackageDownloader(); - var workloadResolver = new MockWorkloadResolver([new WorkloadInfo(new WorkloadId("android"), string.Empty)], getPacks: id => [], installedManifests: []); + var manifestProvider = new MockManifestProvider(Array.Empty()) { SdkFeatureBand = new SdkFeatureBand(sdkVersion) }; + var workloadResolver = new MockWorkloadResolver([new WorkloadInfo(new WorkloadId("android"), string.Empty)], getPacks: id => [], installedManifests: [], manifestProvider: manifestProvider); var workloadInstaller = new MockPackWorkloadInstaller( dotnetDir, installedWorkloads: [new WorkloadId("android")], @@ -375,13 +378,16 @@ public void GivenWorkloadUpdateItFindsGreatestWorkloadSetWithSpecifiedComponents WorkloadManifest iosManifest = WorkloadManifest.CreateForTests("Microsoft.NET.Sdk.iOS"); WorkloadManifest macosManifest = WorkloadManifest.CreateForTests("Microsoft.NET.Sdk.macOS"); WorkloadManifest mauiManifest = WorkloadManifest.CreateForTests("Microsoft.NET.Sdk.Maui"); + var manifestProvider = new MockManifestProvider(Array.Empty()) { SdkFeatureBand = new SdkFeatureBand("9.0.100") }; + MockWorkloadResolver resolver = new([new WorkloadInfo(new WorkloadId("ios"), ""), new WorkloadInfo(new WorkloadId("macos"), ""), new WorkloadInfo(new WorkloadId("maui"), "")], installedManifests: [ new WorkloadManifestInfo("Microsoft.NET.Sdk.iOS", "17.4.3", Path.Combine(testDirectory, "iosManifest"), "9.0.100"), new WorkloadManifestInfo("Microsoft.NET.Sdk.macOS", "14.4.3", Path.Combine(testDirectory, "macosManifest"), "9.0.100"), new WorkloadManifestInfo("Microsoft.NET.Sdk.Maui", "14.4.3", Path.Combine(testDirectory, "mauiManifest"), "9.0.100") ], - getManifest: id => id.ToString().Equals("ios") ? iosManifest : id.ToString().Equals("macos") ? macosManifest : mauiManifest); + getManifest: id => id.ToString().Equals("ios") ? iosManifest : id.ToString().Equals("macos") ? macosManifest : mauiManifest, + manifestProvider: manifestProvider); MockNuGetPackageDownloader nugetPackageDownloader = new(packageVersions: [new NuGetVersion("9.103.0"), new NuGetVersion("9.102.0"), new NuGetVersion("9.101.0"), new NuGetVersion("9.100.0")]); WorkloadUpdateCommand command = new( parseResult, @@ -654,5 +660,55 @@ public void GivenInvalidVersionInRollbackFileItErrors() return (dotnetRoot, installManager, installer, workloadResolver, manifestUpdater, nugetDownloader, workloadResolverFactory); } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void GivenMissingManifestsInWorkloadSetModeUpdateReinstallsManifests(bool userLocal) + { + var (dotnetRoot, userProfileDir, mockInstaller, workloadResolver, manifestProvider) = + CorruptWorkloadSetTestHelper.SetupCorruptWorkloadSet(_testAssetsManager, userLocal, out string sdkFeatureVersion); + + mockInstaller.InstalledManifests.Should().HaveCount(0); + + var workloadResolverFactory = new MockWorkloadResolverFactory(dotnetRoot, sdkFeatureVersion, workloadResolver, userProfileDir); + + // Attach the corruption repairer to the manifest provider + var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot, manifestDownload: true); + manifestProvider.CorruptionRepairer = new WorkloadManifestCorruptionRepairer( + _reporter, + mockInstaller, + workloadResolver, + new SdkFeatureBand(sdkFeatureVersion), + dotnetRoot, + userProfileDir, + nugetDownloader, + packageSourceLocation: null, + VerbosityOptions.detailed); + + // Advertise a NEWER workload set version than what's currently installed (6.0.100) + // so that the update command proceeds with manifest installation + var workloadManifestUpdater = new MockWorkloadManifestUpdater( + manifestUpdates: [ + new ManifestUpdateWithWorkloads(new ManifestVersionUpdate(new ManifestId("xamarin-android-build"), new ManifestVersion("8.4.8"), "6.0.100"), Enumerable.Empty>().ToDictionary()), + new ManifestUpdateWithWorkloads(new ManifestVersionUpdate(new ManifestId("xamarin-ios-sdk"), new ManifestVersion("10.0.2"), "6.0.100"), Enumerable.Empty>().ToDictionary()) + ], + fromWorkloadSet: true, workloadSetVersion: "6.0.101"); + + var parseResult = Parser.Parse(new string[] { "dotnet", "workload", "update" }); + + // Run update command + var updateCommand = new WorkloadUpdateCommand(parseResult, reporter: _reporter, workloadResolverFactory, + workloadInstaller: mockInstaller, workloadManifestUpdater: workloadManifestUpdater); + updateCommand.Execute(); + + // Verify that manifests were reinstalled + mockInstaller.InstalledManifests.Should().HaveCount(2); + mockInstaller.InstalledManifests.Should().Contain(m => m.manifestUpdate.ManifestId.ToString() == "xamarin-android-build"); + mockInstaller.InstalledManifests.Should().Contain(m => m.manifestUpdate.ManifestId.ToString() == "xamarin-ios-sdk"); + + // Verify command succeeded + _reporter.Lines.Should().NotContain(line => line.Contains("failed", StringComparison.OrdinalIgnoreCase)); + } } }