diff --git a/eng/Versions.props b/eng/Versions.props index bdd579abb852..705bbfc33b22 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -37,8 +37,8 @@ 36 20 - 22 - 11 + 23 + 12 <_NET70ILLinkPackVersion>7.0.100-1.23211.1 diff --git a/src/Cli/dotnet/Commands/CliCommandStrings.resx b/src/Cli/dotnet/Commands/CliCommandStrings.resx index c1d47a523067..726121c90abf 100644 --- a/src/Cli/dotnet/Commands/CliCommandStrings.resx +++ b/src/Cli/dotnet/Commands/CliCommandStrings.resx @@ -1572,4 +1572,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"} + \ No newline at end of file diff --git a/src/Cli/dotnet/Commands/Test/MTP/TestApplication.cs b/src/Cli/dotnet/Commands/Test/MTP/TestApplication.cs index fb37be9fea9b..29ffd83eb344 100644 --- a/src/Cli/dotnet/Commands/Test/MTP/TestApplication.cs +++ b/src/Cli/dotnet/Commands/Test/MTP/TestApplication.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Globalization; +using System.IO; using System.IO.Pipes; using System.Threading; using Microsoft.DotNet.Cli.Commands.Test.IPC; @@ -129,6 +130,7 @@ private ProcessStartInfo CreateProcessStartInfo() processStartInfo.Environment[Module.DotnetRootArchVariableName] = Path.GetDirectoryName(new Muxer().MuxerPath); } + processStartInfo.Environment["DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY"] = Directory.GetCurrentDirectory(); return processStartInfo; } diff --git a/src/Cli/dotnet/Commands/Workload/Install/FileBasedInstaller.cs b/src/Cli/dotnet/Commands/Workload/Install/FileBasedInstaller.cs index 72d1065a44da..2ecae0d3f478 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; @@ -643,7 +644,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 74337dc143c4..4e523eafb594 100644 --- a/src/Cli/dotnet/Commands/Workload/Repair/WorkloadRepairCommand.cs +++ b/src/Cli/dotnet/Commands/Workload/Repair/WorkloadRepairCommand.cs @@ -70,7 +70,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()) { @@ -80,7 +81,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)); @@ -106,4 +107,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 aca54ec20616..ddc24dfadea5 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf @@ -2207,6 +2207,11 @@ příkazu „dotnet tool list“. 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"} + Installation Source Zdroj instalace diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf index 553a73dc90fc..121ca426e28b 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf @@ -2207,6 +2207,11 @@ und die zugehörigen Paket-IDs für installierte Tools über den Befehl 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"} + Installation Source Installationsquelle diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf index 91c32ff85daa..a348d413d164 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf @@ -2207,6 +2207,11 @@ y los identificadores de los paquetes correspondientes a las herramientas instal 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"} + Installation Source Origen de la instalación diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf index 63eae5969a2f..f06b4973e79e 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf @@ -2207,6 +2207,11 @@ et les ID de package correspondants aux outils installés, utilisez la commande 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"} + Installation Source Source de l’installation diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf index 916c4b11c60e..b3daa1e8e5c5 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf @@ -2207,6 +2207,11 @@ e gli ID pacchetto corrispondenti per gli strumenti installati usando il comando 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"} + Installation Source Origine dell'installazione diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf index 0f07011cd52d..6d4a38052203 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf @@ -2207,6 +2207,11 @@ and the corresponding package Ids for installed tools using the command {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"} + Installation Source インストール ソース diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf index 20b68d1a3182..fd078178129d 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf @@ -2207,6 +2207,11 @@ and the corresponding package Ids for installed tools using the command {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"} + Installation Source 설치 원본 diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf index dda65dfdf1e3..18b3f648d1bc 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf @@ -2207,6 +2207,11 @@ i odpowiednie identyfikatory pakietów zainstalowanych narzędzi można znaleź 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"} + Installation Source Źródło instalacji diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf index c32d0c5c5c99..66c3d19ace1a 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf @@ -2207,6 +2207,11 @@ e as Ids de pacote correspondentes para as ferramentas instaladas usando o coman 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"} + Installation Source Origem da Instalação diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf index 4d108ae99863..07d1733a30b8 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf @@ -2208,6 +2208,11 @@ and the corresponding package Ids for installed tools using the command Версия рабочей нагрузки {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"} + Installation Source Источник установки diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf index 5ec84a882619..5e0d801324bd 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf @@ -2207,6 +2207,11 @@ karşılık gelen paket kimliklerini bulmak için {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"} + Installation Source Yükleme Kaynağı diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf index f1756cdf26a6..ec9d67a63006 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf @@ -2207,6 +2207,11 @@ and the corresponding package Ids for installed tools using the command 找不到在 {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"} + Installation Source 安装源文件 diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf index e739b31721f7..c496d9947662 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf @@ -2207,6 +2207,11 @@ and the corresponding package Ids for installed tools using the command 找不到 {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"} + Installation Source 安裝來源 diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageAndTag.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageAndTag.cs index 92cd3f5a5c85..69ced08eae1e 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageAndTag.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageAndTag.cs @@ -130,7 +130,7 @@ private bool TargetRuntimeIdentiriersAreValid() { if (muslRidsCount == TargetRuntimeIdentifiers.Length) { - IsMuslRid = true; + IsMuslRid = true; } else { @@ -191,7 +191,7 @@ private bool ComputeRepositoryAndTag([NotNullWhen(true)] out string? repository, && !UsesInvariantGlobalization && versionAllowsUsingAOTAndExtrasImages // the extras only became available on the stable tags of the FirstVersionWithNewTaggingScheme - && (!parsedVersion.IsPrerelease && parsedVersion.Major == FirstVersionWithNewTaggingScheme)) + && (!parsedVersion.IsPrerelease && parsedVersion.Major >= FirstVersionWithNewTaggingScheme)) { Log.LogMessage("Using extra variant because the application needs globalization"); tag += "-extra"; diff --git a/src/Microsoft.CodeAnalysis.NetAnalyzers/tools/GenerateDocumentationAndConfigFiles/Program.cs b/src/Microsoft.CodeAnalysis.NetAnalyzers/tools/GenerateDocumentationAndConfigFiles/Program.cs index 3832124d1693..ff7104f3fd0a 100644 --- a/src/Microsoft.CodeAnalysis.NetAnalyzers/tools/GenerateDocumentationAndConfigFiles/Program.cs +++ b/src/Microsoft.CodeAnalysis.NetAnalyzers/tools/GenerateDocumentationAndConfigFiles/Program.cs @@ -270,7 +270,7 @@ void createPropsFiles() fileContents = $""" - @@ -292,7 +292,7 @@ string getDisableNetAnalyzersImport() { return $""" - @@ -318,7 +318,7 @@ string getCodeAnalysisTreatWarningsAsErrors() var allRuleIds = string.Join(';', allRulesById.Keys); return $""" - @@ -1153,7 +1153,7 @@ string getRuleActionCore(bool enable, bool enableAsWarning = false) private static void Validate(string fileWithPath, string fileContents, List fileNamesWithValidationFailures) { string actual = File.ReadAllText(fileWithPath); - if (actual != fileContents) + if (actual.Trim() != fileContents.Trim()) { fileNamesWithValidationFailures.Add(fileWithPath); } @@ -1560,7 +1560,7 @@ static void AddItemGroupForCompilerVisibleProperties(List compilerVisibl { builder.AppendLine($""" - + """); foreach (var compilerVisibleProperty in compilerVisibleProperties) 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 2b13fa2b3329..369b2586d0a9 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/src/SourceBuild/patches/runtime/0001-release-9.0-staging-Add-flags-when-the-clang-s-major.patch b/src/SourceBuild/patches/runtime/0001-release-9.0-staging-Add-flags-when-the-clang-s-major.patch deleted file mode 100644 index 395dea35c1ae..000000000000 --- a/src/SourceBuild/patches/runtime/0001-release-9.0-staging-Add-flags-when-the-clang-s-major.patch +++ /dev/null @@ -1,145 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aaron R Robinson -Date: Fri, 14 Nov 2025 11:01:28 -0800 -Subject: [PATCH] [release/9.0-staging] Add flags when the clang's major - version is > 20.0 (#121151) -Backport: https://github.com/dotnet/runtime/pull/121151 - -## Customer Impact - -- [x] Customer reported -- [ ] Found internally - -These issues were reported in -https://github.com/dotnet/runtime/issues/119706 as problems with -clang-21 on Fedora 43. The investigation uncovered that clang introduced -a potentially breaking change in clang-20 that we do not currently -consume. These build changes impact VMR related builds when linux -distrobutions performing source build adopt clang-21. - -clang-20 breaking change log - -https://releases.llvm.org/20.1.0/tools/clang/docs/ReleaseNotes.html#potentially-breaking-changes. - -This PR contains the minimal changes needed to fix issues from the -following PR https://github.com/dotnet/runtime/pull/120775. - -.NET 10: https://github.com/dotnet/runtime/pull/121124 -.NET 8: https://github.com/dotnet/runtime/pull/121150 - -## Regression - -- [ ] Yes -- [x] No - -Build with the new clang-21 compiler will cause the runtime to crash. - -## Testing - -This has been validated using various legs and examples to demonstrate -the usage of undefined behavior these flags convert into "defined" -behavior in C/C++. - -## Risk - -Low. This has zero impact on our production build since we specifically -target clang-18. This is only valid for those partners that are using -clang-20+. ---- - eng/native/configurecompiler.cmake | 18 +++++++++++++++--- - src/coreclr/debug/di/rspriv.h | 4 ++-- - src/coreclr/debug/di/rsthread.cpp | 12 ++++++------ - 3 files changed, 23 insertions(+), 11 deletions(-) - -diff --git a/eng/native/configurecompiler.cmake b/eng/native/configurecompiler.cmake -index 109b947e4eb..c114c03a9a1 100644 ---- a/eng/native/configurecompiler.cmake -+++ b/eng/native/configurecompiler.cmake -@@ -526,9 +526,21 @@ if (CLR_CMAKE_HOST_UNIX) - # Disable frame pointer optimizations so profilers can get better call stacks - add_compile_options(-fno-omit-frame-pointer) - -- # Make signed arithmetic overflow of addition, subtraction, and multiplication wrap around -- # using twos-complement representation (this is normally undefined according to the C++ spec). -- add_compile_options(-fwrapv) -+ if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 20.0) OR -+ (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 20.0)) -+ # Make signed overflow well-defined. Implies the following flags in clang-20 and above. -+ # -fwrapv - Make signed arithmetic overflow of addition, subtraction, and multiplication wrap around -+ # using twos-complement representation (this is normally undefined according to the C++ spec). -+ # -fwrapv-pointer - The same as -fwrapv but for pointers. -+ add_compile_options(-fno-strict-overflow) -+ -+ # Suppress C++ strict aliasing rules. This matches our use of MSVC. -+ add_compile_options(-fno-strict-aliasing) -+ else() -+ # Make signed arithmetic overflow of addition, subtraction, and multiplication wrap around -+ # using twos-complement representation (this is normally undefined according to the C++ spec). -+ add_compile_options(-fwrapv) -+ endif() - - if(CLR_CMAKE_HOST_APPLE) - # Clang will by default emit objc_msgSend stubs in Xcode 14, which ld from earlier Xcodes doesn't understand. -diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h -index 7e2b49b3170..119ca6f7c08 100644 ---- a/src/coreclr/debug/di/rspriv.h -+++ b/src/coreclr/debug/di/rspriv.h -@@ -6404,8 +6404,8 @@ private: - // Lazily initialized. - EXCEPTION_RECORD * m_pExceptionRecord; - -- static const CorDebugUserState kInvalidUserState = CorDebugUserState(-1); -- CorDebugUserState m_userState; // This is the current state of the -+ static const int kInvalidUserState = -1; -+ int m_userState; // This is the current state of the - // thread, at the time that the - // left side synchronized - -diff --git a/src/coreclr/debug/di/rsthread.cpp b/src/coreclr/debug/di/rsthread.cpp -index cd7f79867a5..8c4f3317eff 100644 ---- a/src/coreclr/debug/di/rsthread.cpp -+++ b/src/coreclr/debug/di/rsthread.cpp -@@ -783,7 +783,7 @@ CorDebugUserState CordbThread::GetUserState() - m_userState = pDAC->GetUserState(m_vmThreadToken); - } - -- return m_userState; -+ return (CorDebugUserState)m_userState; - } - - -@@ -887,7 +887,7 @@ HRESULT CordbThread::CreateStepper(ICorDebugStepper ** ppStepper) - //Returns true if current user state of a thread is USER_WAIT_SLEEP_JOIN - bool CordbThread::IsThreadWaitingOrSleeping() - { -- CorDebugUserState userState = m_userState; -+ int userState = m_userState; - if (userState == kInvalidUserState) - { - //If m_userState is not ready, we'll read from DAC only part of it which -@@ -3721,14 +3721,14 @@ HRESULT CordbUnmanagedThread::SetupFirstChanceHijackForSync() - LOG((LF_CORDB, LL_INFO10000, "CUT::SFCHFS: hijackCtx started as:\n")); - LogContext(GetHijackCtx()); - -- // Save the thread's full context for all platforms except for x86 because we need the -+ // Save the thread's full context for all platforms except for x86 because we need the - // DT_CONTEXT_EXTENDED_REGISTERS to avoid getting incomplete information and corrupt the thread context - DT_CONTEXT context; --#ifdef TARGET_X86 -+#ifdef TARGET_X86 - context.ContextFlags = DT_CONTEXT_FULL | DT_CONTEXT_EXTENDED_REGISTERS; - #else - context.ContextFlags = DT_CONTEXT_FULL; --#endif -+#endif - - BOOL succ = DbiGetThreadContext(m_handle, &context); - _ASSERTE(succ); -@@ -3739,7 +3739,7 @@ HRESULT CordbUnmanagedThread::SetupFirstChanceHijackForSync() - LOG((LF_CORDB, LL_ERROR, "CUT::SFCHFS: DbiGetThreadContext error=0x%x\n", error)); - } - --#ifdef TARGET_X86 -+#ifdef TARGET_X86 - GetHijackCtx()->ContextFlags = DT_CONTEXT_FULL | DT_CONTEXT_EXTENDED_REGISTERS; - #else - GetHijackCtx()->ContextFlags = DT_CONTEXT_FULL; diff --git a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/ProcessFrameworkReferencesTests.cs b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/ProcessFrameworkReferencesTests.cs index 34286cbb468a..cabcd3734b17 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/ProcessFrameworkReferencesTests.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/ProcessFrameworkReferencesTests.cs @@ -82,6 +82,30 @@ public class ProcessFrameworkReferencesTests } """; + private const string NonPortableRid = "fedora.42-x64"; + + private static readonly string NonPortableRuntimeGraph = $$""" + { + "runtimes": { + "base": { + "#import": [] + }, + "any": { + "#import": ["base"] + }, + "linux": { + "#import": ["any"] + }, + "linux-x64": { + "#import": ["linux"] + }, + "{{NonPortableRid}}": { + "#import": ["linux-x64"] + } + } + } + """; + // Shared known framework references private readonly MockTaskItem _validWindowsSDKKnownFrameworkReference = CreateKnownFrameworkReference( "Microsoft.Windows.SDK.NET.Ref", "net5.0-windows10.0.18362", "10.0.18362.1-preview", @@ -493,6 +517,7 @@ public void It_handles_real_world_ridless_scenario_with_aot_and_trimming() ["TargetFramework"] = "net10.0", ["ILCompilerPackNamePattern"] = "runtime.**RID**.Microsoft.DotNet.ILCompiler", ["ILCompilerPackVersion"] = "10.0.0-rc.2.25457.102", + ["ILCompilerPortableRuntimeIdentifiers"] = "osx-x64;osx-arm64;win-x64;linux-x64", ["ILCompilerRuntimeIdentifiers"] = "osx-x64;osx-arm64;win-x64;linux-x64" }); @@ -967,5 +992,52 @@ public void It_handles_complex_cross_compilation_RuntimeIdentifiers() $"Should include runtime pack for supported RID: {rid}"); } } + + [Theory] + [InlineData(null, "linux-x64")] + [InlineData("linux-x64", "linux-x64")] + [InlineData(NonPortableRid, NonPortableRid)] + public void It_selects_correct_ILCompiler_based_on_RuntimeIdentifier(string? runtimeIdentifier, string expectedILCompilerRid) + { + var netCoreAppRef = CreateKnownFrameworkReference("Microsoft.NETCore.App", "net10.0", "10.0.1", + "Microsoft.NETCore.App.Runtime.**RID**", + "linux-x64;" + NonPortableRid); + + var ilCompilerPack = new MockTaskItem("Microsoft.DotNet.ILCompiler", new Dictionary + { + ["TargetFramework"] = "net10.0", + ["ILCompilerPackNamePattern"] = "runtime.**RID**.Microsoft.DotNet.ILCompiler", + ["ILCompilerPackVersion"] = "10.0.1", + ["ILCompilerPortableRuntimeIdentifiers"] = "linux-x64", + ["ILCompilerRuntimeIdentifiers"] = "linux-x64;" + NonPortableRid + }); + + var config = new TaskConfiguration + { + TargetFrameworkVersion = "10.0", + EnableRuntimePackDownload = true, + PublishAot = true, + NETCoreSdkRuntimeIdentifier = NonPortableRid, + NETCoreSdkPortableRuntimeIdentifier = "linux-x64", + RuntimeIdentifier = runtimeIdentifier, + RuntimeGraphPath = CreateRuntimeGraphFile(NonPortableRuntimeGraph), + FrameworkReferences = new[] { new MockTaskItem("Microsoft.NETCore.App", new Dictionary()) }, + KnownFrameworkReferences = new[] { netCoreAppRef }, + KnownILCompilerPacks = new[] { ilCompilerPack } + }; + + var task = CreateTask(config); + task.Execute().Should().BeTrue("Task should succeed"); + + // Validate that the expected ILCompiler pack is used + task.PackagesToDownload.Should().NotBeNull(); + task.PackagesToDownload.Should().Contain(p => p.ItemSpec.Contains("Microsoft.DotNet.ILCompiler"), + "Should include ILCompiler pack when PublishAot is true"); + + var ilCompilerPackage = task.PackagesToDownload.FirstOrDefault(p => p.ItemSpec.Contains("Microsoft.DotNet.ILCompiler")); + ilCompilerPackage.Should().NotBeNull(); + ilCompilerPackage!.ItemSpec.Should().Contain(expectedILCompilerRid, + $"Should use {expectedILCompilerRid} ILCompiler pack"); + } } } diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/ProcessFrameworkReferences.cs b/src/Tasks/Microsoft.NET.Build.Tasks/ProcessFrameworkReferences.cs index 8f3d55dcde4b..e11fc4ebc73f 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/ProcessFrameworkReferences.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/ProcessFrameworkReferences.cs @@ -838,8 +838,10 @@ private ToolPackSupport AddToolPack( if (toolPackType is ToolPackType.Crossgen2 or ToolPackType.ILCompiler) { var packNamePattern = knownPack.GetMetadata(packName + "PackNamePattern"); - var packSupportedRuntimeIdentifiers = knownPack.GetMetadata(packName + "RuntimeIdentifiers").Split(';'); - var packSupportedPortableRuntimeIdentifiers = knownPack.GetMetadata(packName + "PortableRuntimeIdentifiers").Split(';'); + var packSupportedRuntimeIdentifiers = knownPack.GetMetadata(packName + "RuntimeIdentifiers").Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + var packSupportedPortableRuntimeIdentifiers = knownPack.GetMetadata(packName + "PortableRuntimeIdentifiers").Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + // Use the non-portable RIDs if there are no portable RIDs defined. + packSupportedPortableRuntimeIdentifiers = packSupportedPortableRuntimeIdentifiers.Length > 0 ? packSupportedPortableRuntimeIdentifiers : packSupportedRuntimeIdentifiers; // When publishing for a non-portable RID, prefer NETCoreSdkRuntimeIdentifier for the host. // Otherwise prefer the NETCoreSdkPortableRuntimeIdentifier. @@ -853,15 +855,16 @@ private ToolPackSupport AddToolPack( // This also ensures that targeting common RIDs doesn't require any non-portable assets that aren't packaged in the SDK by default. // Due to size concerns, the non-portable ILCompiler and Crossgen2 aren't included by default in non-portable SDK distributions. var runtimeIdentifier = RuntimeIdentifier ?? RuntimeIdentifierForPlatformAgnosticComponents; + string? supportedTargetRid = NuGetUtils.GetBestMatchingRid(runtimeGraph, runtimeIdentifier, packSupportedRuntimeIdentifiers, out _); string? supportedPortableTargetRid = NuGetUtils.GetBestMatchingRid(runtimeGraph, runtimeIdentifier, packSupportedPortableRuntimeIdentifiers, out _); bool usePortable = !string.IsNullOrEmpty(NETCoreSdkPortableRuntimeIdentifier) - && supportedTargetRid is not null && supportedPortableTargetRid is not null && supportedTargetRid == supportedPortableTargetRid; // Get the best RID for the host machine, which will be used to validate that we can run crossgen for the target platform and architecture Log.LogMessage(MessageImportance.Low, $"Determining best RID for '{knownPack.ItemSpec}@{packVersion}' from among '{knownPack.GetMetadata(packName + "RuntimeIdentifiers")}'"); + string? hostRuntimeIdentifier = usePortable ? NuGetUtils.GetBestMatchingRid(runtimeGraph, NETCoreSdkPortableRuntimeIdentifier!, packSupportedPortableRuntimeIdentifiers, out _) : NuGetUtils.GetBestMatchingRid(runtimeGraph, NETCoreSdkRuntimeIdentifier!, packSupportedRuntimeIdentifiers, out _); diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets index e38b16fe46b3..eef7ac8c0b80 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets @@ -847,8 +847,8 @@ Copyright (c) .NET Foundation. All rights reserved. %(Content.TargetPath) %(Content.Link) - $([MSBuild]::MakeRelative(%(Content.DefiningProjectDirectory), %(Content.FullPath))) - $([MSBuild]::MakeRelative($(MSBuildProjectDirectory), %(Content.FullPath))) + + $([MSBuild]::MakeRelative($(MSBuildProjectDirectory), %(Content.FullPath))) diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.RuntimeIdentifierInference.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.RuntimeIdentifierInference.targets index 67edae3e3c3b..27ec11b2412d 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.RuntimeIdentifierInference.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.RuntimeIdentifierInference.targets @@ -127,7 +127,8 @@ Copyright (c) .NET Foundation. All rights reserved. then set a default value for PublishRuntimeIdentifier if RuntimeIdentifier isn't set. However, don't do this if we are packing a PackAsTool project, as the "outer" build should still be without a RuntimeIdentifier --> - + true + + $(NETCoreSdkPortableRuntimeIdentifier) diff --git a/test/Microsoft.NET.Build.Containers.IntegrationTests/TargetsTests.cs b/test/Microsoft.NET.Build.Containers.IntegrationTests/TargetsTests.cs index d0cb12539451..2c6b80ace493 100644 --- a/test/Microsoft.NET.Build.Containers.IntegrationTests/TargetsTests.cs +++ b/test/Microsoft.NET.Build.Containers.IntegrationTests/TargetsTests.cs @@ -26,7 +26,7 @@ public void CanDeferContainerAppCommand( }, projectName: $"{nameof(CanDeferContainerAppCommand)}_{prop}_{value}_{string.Join("_", expectedAppCommandArgs)}"); using var _ = d; var instance = project.CreateProjectInstance(ProjectInstanceSettings.None); - instance.Build([ ComputeContainerConfig ], []); + instance.Build([ComputeContainerConfig], []); var computedAppCommand = instance.GetItems(ContainerAppCommand).Select(i => i.EvaluatedInclude); // The test was not testing anything previously, as the list returned was zero length, @@ -614,6 +614,51 @@ public void AOTAppsLessThan8WithCulturesDoNotGetExtraImages(string rid, string e computedBaseImageTag.Should().BeEquivalentTo(expectedImage); } + [InlineData("8.0.100", "v8.0", "jammy-chiseled", "mcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled-extra")] + [InlineData("9.0.100", "v9.0", "noble-chiseled", "mcr.microsoft.com/dotnet/runtime:9.0-noble-chiseled-extra")] + [InlineData("10.0.100", "v10.0", "noble-chiseled", "mcr.microsoft.com/dotnet/runtime:10.0-noble-chiseled-extra")] + [Theory] + public void FDDConsoleAppWithCulturesAndOptingIntoChiseledGetsExtrasForNet9AndLater(string sdkVersion, string tfm, string containerFamily, string expectedImage) + { + var (project, logger, d) = ProjectInitializer.InitProject(new() + { + ["NetCoreSdkVersion"] = sdkVersion, + ["TargetFrameworkVersion"] = tfm, + [KnownStrings.Properties.ContainerRuntimeIdentifier] = "linux-x64", + [KnownStrings.Properties.ContainerFamily] = containerFamily, + [KnownStrings.Properties.InvariantGlobalization] = false.ToString(), + }, projectName: $"{nameof(FDDConsoleAppWithCulturesAndOptingIntoChiseledGetsExtrasForNet9AndLater)}_{sdkVersion}_{tfm}_{containerFamily}"); + using var _ = d; + var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); + instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors)); + var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue; + computedBaseImageTag.Should().BeEquivalentTo(expectedImage); + } + + [InlineData("8.0.100", "v8.0", "jammy-chiseled", "mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled-extra")] + [InlineData("9.0.100", "v9.0", "noble-chiseled", "mcr.microsoft.com/dotnet/aspnet:9.0-noble-chiseled-extra")] + [InlineData("10.0.100", "v10.0", "noble-chiseled", "mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled-extra")] + [Theory] + public void FDDAspNetAppWithCulturesAndOptingIntoChiseledGetsExtrasForNet9AndLater(string sdkVersion, string tfm, string containerFamily, string expectedImage) + { + var (project, logger, d) = ProjectInitializer.InitProject(new() + { + ["NetCoreSdkVersion"] = sdkVersion, + ["TargetFrameworkVersion"] = tfm, + [KnownStrings.Properties.ContainerRuntimeIdentifier] = "linux-x64", + [KnownStrings.Properties.ContainerFamily] = containerFamily, + [KnownStrings.Properties.InvariantGlobalization] = false.ToString(), + }, bonusItems: new() + { + [KnownStrings.Items.FrameworkReference] = KnownFrameworkReferences.WebApp + }, projectName: $"{nameof(FDDAspNetAppWithCulturesAndOptingIntoChiseledGetsExtrasForNet9AndLater)}_{sdkVersion}_{tfm}_{containerFamily}"); + using var _ = d; + var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); + instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors)); + var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue; + computedBaseImageTag.Should().BeEquivalentTo(expectedImage); + } + [Fact] public void AspNetFDDAppsGetAspNetBaseImage() { diff --git a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs index 2f28bcd53135..237d0af2ae00 100644 --- a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs +++ b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs @@ -51,7 +51,7 @@ public void NativeAot_hw_runs_with_no_warnings_when_PublishAot_is_enabled(string .And.NotHaveStdOutContaining("IL2026") .And.NotHaveStdErrContaining("NETSDK1179") .And.NotHaveStdErrContaining("warning") - .And.NotHaveStdOutContaining("warning"); + .And.NotHaveStdOutContaining("warning", new[] { "ld: warning: -ld_classic is deprecated and will be removed in a future release" }); var buildProperties = testProject.GetPropertyValues(testAsset.TestRoot, targetFramework); var rid = buildProperties["NETCoreSdkPortableRuntimeIdentifier"]; diff --git a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishWithIfDifferent.cs b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishWithIfDifferent.cs index 9ebb72c1b982..d234ec586284 100644 --- a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishWithIfDifferent.cs +++ b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishWithIfDifferent.cs @@ -381,5 +381,79 @@ public void It_handles_IfDifferent_with_self_contained_publish() publishDirectory.Should().HaveFile("appdata.txt"); File.ReadAllText(Path.Combine(publishDirectory.FullName, "appdata.txt")).Should().Be("Application data"); } + + [Fact] + public void It_publishes_content_from_imported_targets_with_correct_path() + { + // This test verifies that Content items introduced from imported .targets files + // (where DefiningProjectDirectory differs from MSBuildProjectDirectory) are + // published with the correct project-relative path and do not escape the publish directory. + // + // The bug scenario: when a .targets file OUTSIDE the project directory adds a Content item, + // using DefiningProjectDirectory to compute the relative path can result in paths with '..' + // segments that escape the publish directory. + + var testProject = new TestProject() + { + Name = "PublishImportedContent", + TargetFrameworks = ToolsetInfo.CurrentTargetFramework, + IsExe = true + }; + + testProject.SourceFiles["Program.cs"] = "class Program { static void Main() { } }"; + + var testAsset = _testAssetsManager.CreateTestProject(testProject); + + var projectDirectory = Path.Combine(testAsset.Path, testProject.Name); + + // Create the imported targets file OUTSIDE the project directory (sibling folder) + // This is the key difference - DefiningProjectDirectory will be different from MSBuildProjectDirectory + var externalImportsDir = Path.Combine(testAsset.Path, "ExternalImports"); + Directory.CreateDirectory(externalImportsDir); + + // Create the content file in the project directory (where we want it to be published from) + var contentFile = Path.Combine(projectDirectory, "project-content.txt"); + File.WriteAllText(contentFile, "Content defined by external targets"); + + // Create an imported .targets file OUTSIDE the project that adds a Content item + // pointing to a file in the project directory. The issue is that DefiningProjectDirectory + // will be ExternalImports/, not the project directory. + var importedTargetsFile = Path.Combine(externalImportsDir, "ImportedContent.targets"); + File.WriteAllText(importedTargetsFile, $@" + + + + +"); + + // Update the main project file to import the external targets + var projectFile = Path.Combine(projectDirectory, $"{testProject.Name}.csproj"); + var projectContent = File.ReadAllText(projectFile); + projectContent = projectContent.Replace("", @" + +"); + File.WriteAllText(projectFile, projectContent); + + var publishCommand = new PublishCommand(testAsset); + var publishResult = publishCommand.Execute(); + + publishResult.Should().Pass(); + + var publishDirectory = publishCommand.GetOutputDirectory(testProject.TargetFrameworks); + + // The content file should be published with a simple filename, not with path segments + // that could escape the publish directory (e.g., "..\PublishImportedContent\project-content.txt") + publishDirectory.Should().HaveFile("project-content.txt"); + + // Verify the content is correct + var publishedContentPath = Path.Combine(publishDirectory.FullName, "project-content.txt"); + File.ReadAllText(publishedContentPath).Should().Be("Content defined by external targets"); + + // Ensure no files escaped to parent directories + var parentDir = Directory.GetParent(publishDirectory.FullName); + var potentialEscapedFiles = Directory.GetFiles(parentDir.FullName, "project-content.txt", SearchOption.AllDirectories) + .Where(f => !f.StartsWith(publishDirectory.FullName)); + potentialEscapedFiles.Should().BeEmpty("Content file should not escape to directories outside publish folder"); + } } } 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-watch.Tests/HotReload/ApplyDeltaTests.cs b/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs index 709f3fbf206d..792ced5064af 100644 --- a/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs +++ b/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs @@ -516,9 +516,10 @@ public async Task AutoRestartOnRudeEditAfterRestartPrompt() // rude edit: adding virtual method UpdateSourceFile(programPath, src => src.Replace("/* member placeholder */", "public virtual void F() {}")); + // the prompt is printed into stdout while the error is printed into stderr, so they might arrive in any order: await App.AssertOutputLineStartsWith(" ❔ Do you want to restart your app? Yes (y) / No (n) / Always (a) / Never (v)", failure: _ => false); + await App.WaitUntilOutputContains(MessageDescriptor.RestartNeededToApplyChanges); - App.AssertOutputContains(MessageDescriptor.RestartNeededToApplyChanges); App.AssertOutputContains($"❌ {programPath}(39,11): error ENC0023: Adding an abstract method or overriding an inherited method requires restarting the application."); App.Process.ClearOutput(); @@ -1259,9 +1260,10 @@ public async Task Aspire_BuildError_ManualRestart() serviceSourcePath, serviceSource.Replace("record WeatherForecast", "record WeatherForecast2")); + // the prompt is printed into stdout while the error is printed into stderr, so they might arrive in any order: await App.WaitForOutputLineContaining(" ❔ Do you want to restart these projects? Yes (y) / No (n) / Always (a) / Never (v)"); + await App.WaitUntilOutputContains(MessageDescriptor.RestartNeededToApplyChanges); - App.AssertOutputContains(MessageDescriptor.RestartNeededToApplyChanges); App.AssertOutputContains($"dotnet watch ❌ {serviceSourcePath}(40,1): error ENC0020: Renaming record 'WeatherForecast' requires restarting the application."); App.AssertOutputContains("dotnet watch ⌚ Affected projects:"); App.AssertOutputContains("dotnet watch ⌚ WatchAspire.ApiService"); 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/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 f2d30f3dc299..91fa08c1bd83 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)); + } } } diff --git a/test/trustedroots.Tests/trustedroots.Tests.csproj b/test/trustedroots.Tests/trustedroots.Tests.csproj index 1d0caef20291..c4fd805c72af 100644 --- a/test/trustedroots.Tests/trustedroots.Tests.csproj +++ b/test/trustedroots.Tests/trustedroots.Tests.csproj @@ -5,7 +5,6 @@ $(ToolsetTargetFramework) Exe false - $(TestHostFolder)