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 @@
3620
- 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 SourceZdroj 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 SourceInstallationsquelle
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 SourceOrigen 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 SourceSource 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 SourceOrigine 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 SourceOrigem 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 SourceYü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)Exefalse
- $(TestHostFolder)