diff --git a/src/Dotnet.Watch/Watch/AppModels/HotReloadAppModel.cs b/src/Dotnet.Watch/Watch/AppModels/HotReloadAppModel.cs
index 2271f307808d..c1a4bf8104fd 100644
--- a/src/Dotnet.Watch/Watch/AppModels/HotReloadAppModel.cs
+++ b/src/Dotnet.Watch/Watch/AppModels/HotReloadAppModel.cs
@@ -64,39 +64,50 @@ internal static bool IsManagedAgentSupported(ProjectGraphNode project, ILogger l
return false;
}
- // If property is not specified startup hook is enabled:
- // https://github.com/dotnet/runtime/blob/4b0b7238ba021b610d3963313b4471517108d2bc/src/libraries/System.Private.CoreLib/src/System/StartupHookProvider.cs#L22
- // Startup hooks are not used for WASM projects.
- //
- // TODO: Remove once implemented: https://github.com/dotnet/runtime/issues/123778
- if (!project.ProjectInstance.GetBooleanPropertyValue(PropertyNames.StartupHookSupport, defaultValue: true) &&
- !project.GetCapabilities().Contains(ProjectCapability.WebAssembly))
- {
- // Report which property is causing lack of support for startup hooks:
- var (propertyName, propertyValue) =
- project.ProjectInstance.GetBooleanPropertyValue(PropertyNames.PublishAot)
- ? (PropertyNames.PublishAot, true)
- : project.ProjectInstance.GetBooleanPropertyValue(PropertyNames.PublishTrimmed)
- ? (PropertyNames.PublishTrimmed, true)
- : (PropertyNames.StartupHookSupport, false);
-
- logger.Log(MessageDescriptor.ProjectDoesNotSupportHotReload_Property, propertyName, propertyValue.ToString(), PropertyNames.StartupHookSupport, "True");
- return false;
- }
-
- // MetadataUpdaterSupport is the primary indicator of whether the runtime supports Hot Reload.
- // It is however not correctly set prior to .NET 11, so we use Optimize and DebugSymbols instead for older frameworks.
- // See https://github.com/dotnet/runtime/pull/127163
+ // Since .NET 11 the SDK generates runtimeconfig.dev.json file that configures the runtime to support
+ // startup hooks and metadata update handlers in Debug builds.
+ // The file is generated when the property EnableHotReloadInRuntimeConfigDevFile is true.
if (project.IsNetCoreApp(Versions.Version11_0))
{
- if (!project.ProjectInstance.GetBooleanPropertyValue(PropertyNames.MetadataUpdaterSupport, defaultValue: true))
+ if (!project.ProjectInstance.GetBooleanPropertyValue(PropertyNames.EnableHotReloadInRuntimeConfigDevFile, defaultValue: true))
{
- logger.Log(MessageDescriptor.ProjectDoesNotSupportHotReload_Property, PropertyNames.MetadataUpdaterSupport, "False", PropertyNames.MetadataUpdaterSupport, "True");
- return false;
+ // If runtimeconfig.dev.json file is not generated MetadataUpdaterSupport and StartupHookSupport need to be enabled.
+ var metadataUpdaterSupported = project.ProjectInstance.GetBooleanPropertyValue(PropertyNames.MetadataUpdaterSupport, defaultValue: true);
+ if (!metadataUpdaterSupported || !StartupHookSupportedIfRequired())
+ {
+ logger.Log(
+ MessageDescriptor.ProjectDoesNotSupportHotReload_Property,
+ // setting blocking Hot Reload:
+ metadataUpdaterSupported ? PropertyNames.StartupHookSupport : PropertyNames.MetadataUpdaterSupport,
+ "False",
+ // recommended setting:
+ PropertyNames.EnableHotReloadInRuntimeConfigDevFile,
+ "True");
+
+ return false;
+ }
}
}
else
{
+ if (!StartupHookSupportedIfRequired())
+ {
+ // Report which property is causing lack of support for startup hooks:
+ var (propertyName, propertyValue) =
+ project.ProjectInstance.GetBooleanPropertyValue(PropertyNames.PublishAot)
+ ? (PropertyNames.PublishAot, true)
+ : project.ProjectInstance.GetBooleanPropertyValue(PropertyNames.PublishTrimmed)
+ ? (PropertyNames.PublishTrimmed, true)
+ : (PropertyNames.StartupHookSupport, false);
+
+ logger.Log(MessageDescriptor.ProjectDoesNotSupportHotReload_Property, propertyName, propertyValue.ToString(), PropertyNames.StartupHookSupport, "True");
+ return false;
+ }
+
+ // Not checking MetadataUpdateSupport since it's not set correctly prior .NET 11.
+ // Use Optimize and DebugSymbols instead for older frameworks.
+ // See https://github.com/dotnet/runtime/pull/127163
+
if (project.ProjectInstance.GetBooleanPropertyValue(PropertyNames.Optimize))
{
logger.Log(MessageDescriptor.ProjectDoesNotSupportHotReload_Property, PropertyNames.Optimize, "True", PropertyNames.Optimize, "False");
@@ -111,5 +122,10 @@ internal static bool IsManagedAgentSupported(ProjectGraphNode project, ILogger l
}
return true;
+
+ bool StartupHookSupportedIfRequired()
+ => project.ProjectInstance.GetBooleanPropertyValue(PropertyNames.StartupHookSupport, defaultValue: true) ||
+ // Startup hooks are not used for WASM projects.
+ project.GetCapabilities().Contains(ProjectCapability.WebAssembly);
}
}
diff --git a/src/Dotnet.Watch/Watch/Build/BuildNames.cs b/src/Dotnet.Watch/Watch/Build/BuildNames.cs
index 0d294434fb45..d2cedc4644f6 100644
--- a/src/Dotnet.Watch/Watch/Build/BuildNames.cs
+++ b/src/Dotnet.Watch/Watch/Build/BuildNames.cs
@@ -29,6 +29,7 @@ internal static class PropertyNames
public const string DebugSymbols = nameof(DebugSymbols);
public const string PublishTrimmed = nameof(PublishTrimmed);
public const string PublishAot = nameof(PublishAot);
+ public const string EnableHotReloadInRuntimeConfigDevFile = nameof(EnableHotReloadInRuntimeConfigDevFile);
}
internal static class ItemNames
diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/GenerateRuntimeConfigurationFiles.cs b/src/Tasks/Microsoft.NET.Build.Tasks/GenerateRuntimeConfigurationFiles.cs
index 2cb01f0d8774..b36a48dd19b7 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/GenerateRuntimeConfigurationFiles.cs
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/GenerateRuntimeConfigurationFiles.cs
@@ -53,7 +53,18 @@ public class GenerateRuntimeConfigurationFiles : TaskBase, IMultiThreadableTask
public bool WriteIncludedFrameworks { get; set; }
- public bool GenerateRuntimeConfigDevFile { get; set; }
+ ///
+ /// True to generate probing paths to runtimeconfig.dev.json file.
+ ///
+ public bool GenerateProbingPathsToRuntimeConfigDevFile { get; set; }
+
+ ///
+ /// True to generate switches that enable Hot Reload to runtimeconfig.dev.json file.
+ ///
+ public bool GenerateHotReloadRuntimeOptionsToRuntimeConfigDevFile { get; set; }
+
+ private bool GenerateRuntimeConfigDevFile =>
+ GenerateProbingPathsToRuntimeConfigDevFile || GenerateHotReloadRuntimeOptionsToRuntimeConfigDevFile;
public bool AlwaysIncludeCoreFramework { get; set; }
@@ -84,7 +95,7 @@ protected override void ExecuteCore()
// If we want to generate the runtimeconfig.dev.json file
// and we have additional probing paths to add to it
// BUT the runtimeconfigdevpath is empty, log a warning.
- if (GenerateRuntimeConfigDevFile && AdditionalProbingPaths?.Any() == true && string.IsNullOrEmpty(RuntimeConfigDevPath))
+ if (GenerateProbingPathsToRuntimeConfigDevFile && AdditionalProbingPaths?.Any() == true && string.IsNullOrEmpty(RuntimeConfigDevPath))
{
Log.LogWarning(Strings.SkippingAdditionalProbingPaths);
}
@@ -342,7 +353,17 @@ private void WriteDevRuntimeConfig(IList packageFolders)
RuntimeOptions = new RuntimeOptions()
};
- AddAdditionalProbingPaths(devConfig.RuntimeOptions, packageFolders);
+ if (GenerateProbingPathsToRuntimeConfigDevFile)
+ {
+ AddAdditionalProbingPaths(devConfig.RuntimeOptions, packageFolders);
+ }
+
+ if (GenerateHotReloadRuntimeOptionsToRuntimeConfigDevFile)
+ {
+ JsonObject configProperties = GetConfigProperties(devConfig.RuntimeOptions);
+ configProperties["System.Reflection.Metadata.MetadataUpdater.IsSupported"] = true;
+ configProperties["System.StartupHookProvider.IsSupported"] = true;
+ }
WriteToJsonFile(TaskEnvironment.GetAbsolutePath(RuntimeConfigDevPath), devConfig);
_filesWritten.Add(new TaskItem(RuntimeConfigDevPath));
diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets
index 578169f70f00..b301ef6fed0f 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets
@@ -74,11 +74,18 @@ Copyright (c) .NET Foundation. All rights reserved.
+
+
+ true
+
+
- true
- false
+ true
+ true
+
+ true
@@ -86,7 +93,7 @@ Copyright (c) .NET Foundation. All rights reserved.
$(TargetDir)$(ProjectDepsFileName)
$(AssemblyName).runtimeconfig.json
$(TargetDir)$(ProjectRuntimeConfigFileName)
- $(TargetDir)$(AssemblyName).runtimeconfig.dev.json
+ $(TargetDir)$(AssemblyName).runtimeconfig.dev.json
true
true
@@ -354,6 +361,8 @@ Copyright (c) .NET Foundation. All rights reserved.
<_GenerateRuntimeConfigurationPropertyInputsCacheToHash Include="$(SelfContained)"/>
<_GenerateRuntimeConfigurationPropertyInputsCacheToHash Include="$(TargetFramework)"/>
<_GenerateRuntimeConfigurationPropertyInputsCacheToHash Include="$(UserRuntimeConfig)"/>
+ <_GenerateRuntimeConfigurationPropertyInputsCacheToHash Include="$(GenerateProbingPathsToRuntimeConfigDevFile)"/>
+ <_GenerateRuntimeConfigurationPropertyInputsCacheToHash Include="$(EnableHotReloadInRuntimeConfigDevFile)"/>
<_GenerateRuntimeConfigurationPropertyInputsCacheToHash Include="$(_WriteIncludedFrameworks)"/>
@@ -415,7 +424,8 @@ Copyright (c) .NET Foundation. All rights reserved.
AdditionalProbingPaths="@(AdditionalProbingPath)"
IsSelfContained="$(SelfContained)"
WriteIncludedFrameworks="$(_WriteIncludedFrameworks)"
- GenerateRuntimeConfigDevFile="$(GenerateRuntimeConfigDevFile)"
+ GenerateProbingPathsToRuntimeConfigDevFile="$(GenerateProbingPathsToRuntimeConfigDevFile)"
+ GenerateHotReloadRuntimeOptionsToRuntimeConfigDevFile="$(EnableHotReloadInRuntimeConfigDevFile)"
AlwaysIncludeCoreFramework="$(AlwaysIncludeCoreFrameworkInRuntimeConfig)">
diff --git a/test/Microsoft.DotNet.HotReload.Test.Utilities/WatchableApp.cs b/test/Microsoft.DotNet.HotReload.Test.Utilities/WatchableApp.cs
index fc190fa25b25..bb34e461e0bc 100644
--- a/test/Microsoft.DotNet.HotReload.Test.Utilities/WatchableApp.cs
+++ b/test/Microsoft.DotNet.HotReload.Test.Utilities/WatchableApp.cs
@@ -119,31 +119,31 @@ public async ValueTask WaitUntilOutputContains(MessageDescriptor descrip
return matchingLine;
}
- public Task WaitForOutputLineContaining(string text, [CallerFilePath] string? testPath = null, [CallerLineNumber] int testLine = 0)
+ public async Task WaitForOutputLineContaining(string text, [CallerFilePath] string? testPath = null, [CallerLineNumber] int testLine = 0)
{
LogWaitingForOutput(text, testPath, testLine);
- var line = Process.GetRequiredOutputLineAsync(line => line.Contains(text));
+ var line = await Process.GetRequiredOutputLineAsync(line => line.Contains(text));
LogFoundOutput(text, testPath, testLine);
return line;
}
- public Task WaitForOutputLineContaining(MessageDescriptor descriptor, string? projectDisplay = null, [CallerLineNumber] int testLine = 0, [CallerFilePath] string? testPath = null)
+ public async Task WaitForOutputLineContaining(MessageDescriptor descriptor, string? projectDisplay = null, [CallerLineNumber] int testLine = 0, [CallerFilePath] string? testPath = null)
{
var pattern = GetPattern(descriptor, projectDisplay, out var patternDisplay);
LogWaitingForOutput(patternDisplay, testPath, testLine);
- var line = Process.GetRequiredOutputLineAsync(line => pattern.IsMatch(line));
+ var line = await Process.GetRequiredOutputLineAsync(line => pattern.IsMatch(line));
LogFoundOutput(patternDisplay, testPath, testLine);
return line;
}
- public Task WaitForOutputLineContaining(Regex pattern, [CallerFilePath] string? testPath = null, [CallerLineNumber] int testLine = 0)
+ public async Task WaitForOutputLineContaining(Regex pattern, [CallerFilePath] string? testPath = null, [CallerLineNumber] int testLine = 0)
{
var patternDisplay = pattern.ToString();
LogWaitingForOutput(patternDisplay, testPath, testLine);
- var line = Process.GetRequiredOutputLineAsync(line => pattern.IsMatch(line));
+ var line = await Process.GetRequiredOutputLineAsync(line => pattern.IsMatch(line));
LogFoundOutput(patternDisplay, testPath, testLine);
return line;
diff --git a/test/Microsoft.NET.Build.Tasks.Tests/GivenAGenerateRuntimeConfigMultiThreading.cs b/test/Microsoft.NET.Build.Tasks.Tests/GivenAGenerateRuntimeConfigMultiThreading.cs
index 1eb6e85b6f5b..6a069c9d2548 100644
--- a/test/Microsoft.NET.Build.Tasks.Tests/GivenAGenerateRuntimeConfigMultiThreading.cs
+++ b/test/Microsoft.NET.Build.Tasks.Tests/GivenAGenerateRuntimeConfigMultiThreading.cs
@@ -38,7 +38,6 @@ public void ItWritesRuntimeConfigViaTaskEnvironment()
RuntimeConfigPath = configRelativePath,
RuntimeFrameworks = new ITaskItem[] { runtimeFramework },
IsSelfContained = false,
- GenerateRuntimeConfigDevFile = false,
};
// Execute — should write runtimeconfig.json under projectDir via TaskEnvironment
@@ -143,7 +142,6 @@ private static (bool? result, MockBuildEngine engine, Exception? exception) RunT
RuntimeConfigPath = runtimeConfigPath,
RuntimeFrameworks = new ITaskItem[] { runtimeFramework },
IsSelfContained = false,
- GenerateRuntimeConfigDevFile = false,
AssetsFilePath = assetsFilePath,
TaskEnvironment = taskEnvironment,
};
@@ -300,6 +298,276 @@ public void UserRuntimeConfigProducesSameOutputInBothEnvironments()
}
}
+ [TestMethod]
+ public void ItGeneratesHotReloadPropertiesToDevConfig()
+ {
+ // When GenerateHotReloadRuntimeOptionsToRuntimeConfigDevFile = true,
+ // the task should write MetadataUpdater.IsSupported and StartupHookProvider.IsSupported
+ // to the runtimeconfig.dev.json file.
+ var projectDir = Path.Combine(Path.GetTempPath(), "rtconfig-hotreload-" + Guid.NewGuid().ToString("N"));
+ Directory.CreateDirectory(projectDir);
+ try
+ {
+ var configRelativePath = Path.Combine("bin", "test.runtimeconfig.json");
+ var devConfigRelativePath = Path.Combine("bin", "test.runtimeconfig.dev.json");
+ var devConfigAbsolutePath = Path.Combine(projectDir, devConfigRelativePath);
+ Directory.CreateDirectory(Path.Combine(projectDir, "bin"));
+
+ var assetsFilePath = CreateMinimalAssetsFile(projectDir);
+
+ var runtimeFramework = new TaskItem("Microsoft.NETCore.App");
+ runtimeFramework.SetMetadata("FrameworkName", "Microsoft.NETCore.App");
+ runtimeFramework.SetMetadata("Version", "8.0.0");
+
+ var mockEngine = new MockBuildEngine();
+ var task = new GenerateRuntimeConfigurationFiles
+ {
+ BuildEngine = mockEngine,
+ TaskEnvironment = TaskEnvironmentHelper.CreateForTest(projectDir),
+ TargetFramework = "net8.0",
+ TargetFrameworkMoniker = ".NETCoreApp,Version=v8.0",
+ RuntimeConfigPath = configRelativePath,
+ RuntimeConfigDevPath = devConfigRelativePath,
+ RuntimeFrameworks = new ITaskItem[] { runtimeFramework },
+ IsSelfContained = false,
+ AssetsFilePath = assetsFilePath,
+ GenerateHotReloadRuntimeOptionsToRuntimeConfigDevFile = true,
+ };
+
+ task.Execute().Should().BeTrue(
+ string.Join("; ", mockEngine.Errors.Select(e => e.Message)));
+
+ File.Exists(devConfigAbsolutePath).Should().BeTrue(
+ "runtimeconfig.dev.json should be generated when hot reload options are enabled");
+
+ var devContent = File.ReadAllText(devConfigAbsolutePath);
+ devContent.Should().Contain("System.Reflection.Metadata.MetadataUpdater.IsSupported",
+ "hot reload MetadataUpdater switch should be present in dev config");
+ devContent.Should().Contain("System.StartupHookProvider.IsSupported",
+ "hot reload StartupHookProvider switch should be present in dev config");
+ }
+ finally
+ {
+ Directory.Delete(projectDir, true);
+ }
+ }
+
+ [TestMethod]
+ public void ItDoesNotGenerateHotReloadPropertiesWhenDisabled()
+ {
+ // When GenerateHotReloadRuntimeOptionsToRuntimeConfigDevFile = false (default)
+ // and GenerateProbingPathsToRuntimeConfigDevFile = true,
+ // the dev config should NOT contain Hot Reload properties.
+ var projectDir = Path.Combine(Path.GetTempPath(), "rtconfig-nohotreload-" + Guid.NewGuid().ToString("N"));
+ Directory.CreateDirectory(projectDir);
+ try
+ {
+ var configRelativePath = Path.Combine("bin", "test.runtimeconfig.json");
+ var devConfigRelativePath = Path.Combine("bin", "test.runtimeconfig.dev.json");
+ var devConfigAbsolutePath = Path.Combine(projectDir, devConfigRelativePath);
+ Directory.CreateDirectory(Path.Combine(projectDir, "bin"));
+
+ var assetsFilePath = CreateMinimalAssetsFile(projectDir);
+
+ var runtimeFramework = new TaskItem("Microsoft.NETCore.App");
+ runtimeFramework.SetMetadata("FrameworkName", "Microsoft.NETCore.App");
+ runtimeFramework.SetMetadata("Version", "8.0.0");
+
+ var probingPath = new TaskItem("C:\\packages");
+
+ var mockEngine = new MockBuildEngine();
+ var task = new GenerateRuntimeConfigurationFiles
+ {
+ BuildEngine = mockEngine,
+ TaskEnvironment = TaskEnvironmentHelper.CreateForTest(projectDir),
+ TargetFramework = "net8.0",
+ TargetFrameworkMoniker = ".NETCoreApp,Version=v8.0",
+ RuntimeConfigPath = configRelativePath,
+ RuntimeConfigDevPath = devConfigRelativePath,
+ RuntimeFrameworks = new ITaskItem[] { runtimeFramework },
+ IsSelfContained = false,
+ AssetsFilePath = assetsFilePath,
+ GenerateProbingPathsToRuntimeConfigDevFile = true,
+ GenerateHotReloadRuntimeOptionsToRuntimeConfigDevFile = false,
+ AdditionalProbingPaths = new ITaskItem[] { probingPath },
+ };
+
+ task.Execute().Should().BeTrue(
+ string.Join("; ", mockEngine.Errors.Select(e => e.Message)));
+
+ File.Exists(devConfigAbsolutePath).Should().BeTrue(
+ "runtimeconfig.dev.json should be generated when probing paths are enabled");
+
+ var devContent = File.ReadAllText(devConfigAbsolutePath);
+ devContent.Should().NotContain("System.Reflection.Metadata.MetadataUpdater.IsSupported",
+ "hot reload MetadataUpdater switch should NOT be present when disabled");
+ devContent.Should().NotContain("System.StartupHookProvider.IsSupported",
+ "hot reload StartupHookProvider switch should NOT be present when disabled");
+ devContent.Should().Contain("additionalProbingPaths",
+ "probing paths should still be present");
+ }
+ finally
+ {
+ Directory.Delete(projectDir, true);
+ }
+ }
+
+ [TestMethod]
+ public void ItGeneratesBothHotReloadAndProbingPathsWhenBothEnabled()
+ {
+ // When both flags are true, the dev config should contain both
+ // hot reload properties and additional probing paths.
+ var projectDir = Path.Combine(Path.GetTempPath(), "rtconfig-both-" + Guid.NewGuid().ToString("N"));
+ Directory.CreateDirectory(projectDir);
+ try
+ {
+ var configRelativePath = Path.Combine("bin", "test.runtimeconfig.json");
+ var devConfigRelativePath = Path.Combine("bin", "test.runtimeconfig.dev.json");
+ var devConfigAbsolutePath = Path.Combine(projectDir, devConfigRelativePath);
+ Directory.CreateDirectory(Path.Combine(projectDir, "bin"));
+
+ var assetsFilePath = CreateMinimalAssetsFile(projectDir);
+
+ var runtimeFramework = new TaskItem("Microsoft.NETCore.App");
+ runtimeFramework.SetMetadata("FrameworkName", "Microsoft.NETCore.App");
+ runtimeFramework.SetMetadata("Version", "8.0.0");
+
+ var probingPath = new TaskItem("C:\\packages");
+
+ var mockEngine = new MockBuildEngine();
+ var task = new GenerateRuntimeConfigurationFiles
+ {
+ BuildEngine = mockEngine,
+ TaskEnvironment = TaskEnvironmentHelper.CreateForTest(projectDir),
+ TargetFramework = "net8.0",
+ TargetFrameworkMoniker = ".NETCoreApp,Version=v8.0",
+ RuntimeConfigPath = configRelativePath,
+ RuntimeConfigDevPath = devConfigRelativePath,
+ RuntimeFrameworks = new ITaskItem[] { runtimeFramework },
+ IsSelfContained = false,
+ AssetsFilePath = assetsFilePath,
+ GenerateProbingPathsToRuntimeConfigDevFile = true,
+ GenerateHotReloadRuntimeOptionsToRuntimeConfigDevFile = true,
+ AdditionalProbingPaths = new ITaskItem[] { probingPath },
+ };
+
+ task.Execute().Should().BeTrue(
+ string.Join("; ", mockEngine.Errors.Select(e => e.Message)));
+
+ File.Exists(devConfigAbsolutePath).Should().BeTrue(
+ "runtimeconfig.dev.json should be generated when both flags are enabled");
+
+ var devContent = File.ReadAllText(devConfigAbsolutePath);
+ devContent.Should().Contain("System.Reflection.Metadata.MetadataUpdater.IsSupported");
+ devContent.Should().Contain("System.StartupHookProvider.IsSupported");
+ devContent.Should().Contain("additionalProbingPaths");
+ }
+ finally
+ {
+ Directory.Delete(projectDir, true);
+ }
+ }
+
+ [TestMethod]
+ public void ItDoesNotGenerateDevConfigWhenBothFlagsAreFalse()
+ {
+ // When both GenerateProbingPathsToRuntimeConfigDevFile and
+ // GenerateHotReloadRuntimeOptionsToRuntimeConfigDevFile are false,
+ // the computed GenerateRuntimeConfigDevFile should be false,
+ // and no dev config file should be written.
+ var projectDir = Path.Combine(Path.GetTempPath(), "rtconfig-nodev-" + Guid.NewGuid().ToString("N"));
+ Directory.CreateDirectory(projectDir);
+ try
+ {
+ var configRelativePath = Path.Combine("bin", "test.runtimeconfig.json");
+ var devConfigRelativePath = Path.Combine("bin", "test.runtimeconfig.dev.json");
+ var devConfigAbsolutePath = Path.Combine(projectDir, devConfigRelativePath);
+ Directory.CreateDirectory(Path.Combine(projectDir, "bin"));
+
+ var assetsFilePath = CreateMinimalAssetsFile(projectDir);
+
+ var runtimeFramework = new TaskItem("Microsoft.NETCore.App");
+ runtimeFramework.SetMetadata("FrameworkName", "Microsoft.NETCore.App");
+ runtimeFramework.SetMetadata("Version", "8.0.0");
+
+ var mockEngine = new MockBuildEngine();
+ var task = new GenerateRuntimeConfigurationFiles
+ {
+ BuildEngine = mockEngine,
+ TaskEnvironment = TaskEnvironmentHelper.CreateForTest(projectDir),
+ TargetFramework = "net8.0",
+ TargetFrameworkMoniker = ".NETCoreApp,Version=v8.0",
+ RuntimeConfigPath = configRelativePath,
+ RuntimeConfigDevPath = devConfigRelativePath,
+ RuntimeFrameworks = new ITaskItem[] { runtimeFramework },
+ IsSelfContained = false,
+ AssetsFilePath = assetsFilePath,
+ GenerateProbingPathsToRuntimeConfigDevFile = false,
+ GenerateHotReloadRuntimeOptionsToRuntimeConfigDevFile = false,
+ };
+
+ task.Execute().Should().BeTrue(
+ string.Join("; ", mockEngine.Errors.Select(e => e.Message)));
+
+ File.Exists(devConfigAbsolutePath).Should().BeFalse(
+ "runtimeconfig.dev.json should NOT be generated when both flags are false");
+ }
+ finally
+ {
+ Directory.Delete(projectDir, true);
+ }
+ }
+
+ ///
+ /// Creates a minimal project.assets.json file that is sufficient for
+ /// GenerateRuntimeConfigurationFiles to enter the assets-file code path.
+ /// The target key uses short form "net8.0" to match TargetFramework = "net8.0".
+ ///
+ private static string CreateMinimalAssetsFile(string projectDir)
+ {
+ var objDir = Path.Combine(projectDir, "obj");
+ Directory.CreateDirectory(objDir);
+ var assetsPath = Path.Combine(objDir, "project.assets.json");
+ var projectPath = Path.Combine(projectDir, "TestApp.csproj").Replace("\\", "\\\\");
+ var packagesPath = Path.Combine(projectDir, ".nuget", "packages").Replace("\\", "\\\\");
+ var outputPath = Path.Combine(projectDir, "obj").Replace("\\", "\\\\");
+
+ File.WriteAllText(assetsPath, $$"""
+ {
+ "version": 3,
+ "targets": { "net8.0": {} },
+ "libraries": {},
+ "projectFileDependencyGroups": { "net8.0": [] },
+ "packageFolders": {},
+ "project": {
+ "version": "1.0.0",
+ "restore": {
+ "projectUniqueName": "{{projectPath}}",
+ "projectName": "TestApp",
+ "projectPath": "{{projectPath}}",
+ "packagesPath": "{{packagesPath}}",
+ "outputPath": "{{outputPath}}",
+ "projectStyle": "PackageReference",
+ "fallbackFolders": [],
+ "configFilePaths": [],
+ "originalTargetFrameworks": [ "net8.0" ],
+ "sources": {},
+ "frameworks": { "net8.0": { "targetAlias": "net8.0" } },
+ "warningProperties": { "warnAsError": [ "NU1605" ] }
+ },
+ "frameworks": {
+ "net8.0": {
+ "targetAlias": "net8.0"
+ }
+ }
+ }
+ }
+ """);
+
+ // Return relative path since the task will resolve via TaskEnvironment
+ return Path.Combine("obj", "project.assets.json");
+ }
+
[TestMethod]
public void UserRuntimeConfigWithNonexistentFileProducesSameOutputInBothEnvironments()
{
diff --git a/test/Microsoft.NET.Build.Tests/AppHostTests.cs b/test/Microsoft.NET.Build.Tests/AppHostTests.cs
index 3fa818484479..89dd3ff9cf08 100644
--- a/test/Microsoft.NET.Build.Tests/AppHostTests.cs
+++ b/test/Microsoft.NET.Build.Tests/AppHostTests.cs
@@ -7,7 +7,6 @@
using System.Reflection.PortableExecutable;
using System.Text.RegularExpressions;
using Microsoft.DotNet.Cli.Utils;
-using NuGet.Frameworks;
namespace Microsoft.NET.Build.Tests
{
@@ -23,17 +22,12 @@ private static string[] GetExpectedFilesFromBuild(TestAsset testAsset, string ta
$"{testProjectName}.dll",
$"{testProjectName}.pdb",
$"{testProjectName}.deps.json",
- $"{testProjectName}.runtimeconfig.json"
+ $"{testProjectName}.runtimeconfig.json",
+ // Debug builds always generate a runtimeconfig.dev.json file (probing paths for pre-net6.0
+ // target frameworks and Hot Reload runtime options for net6.0+).
+ $"{testProjectName}.runtimeconfig.dev.json"
};
- if (!string.IsNullOrEmpty(targetFramework))
- {
- var parsedTargetFramework = NuGetFramework.Parse(targetFramework);
-
- if (parsedTargetFramework.Version.Major < 6)
- expectedFiles.Add($"{testProjectName}.runtimeconfig.dev.json");
- }
-
return expectedFiles.ToArray();
}
diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantAllResourcesInSatellite.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantAllResourcesInSatellite.cs
index cb8970fdf355..0ee155ac0f1a 100644
--- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantAllResourcesInSatellite.cs
+++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantAllResourcesInSatellite.cs
@@ -82,6 +82,7 @@ internal static void TestSatelliteResources(
outputFiles.Add("AllResourcesInSatellite.dll");
outputFiles.Add("AllResourcesInSatellite.deps.json");
outputFiles.Add("AllResourcesInSatellite.runtimeconfig.json");
+ outputFiles.Add("AllResourcesInSatellite.runtimeconfig.dev.json");
command = new DotnetCommand(log, Path.Combine(outputDirectory.FullName, "AllResourcesInSatellite.dll"));
}
diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantBuildsToBeIncremental.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantBuildsToBeIncremental.cs
index be3ddeffb721..09f59669a285 100644
--- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantBuildsToBeIncremental.cs
+++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantBuildsToBeIncremental.cs
@@ -6,7 +6,6 @@ namespace Microsoft.NET.Build.Tests
[TestClass]
public class GivenThatWeWantBuildsToBeIncremental : SdkTest
{
-
[TestMethod]
[DataRow("netcoreapp1.1")]
[DataRow(ToolsetInfo.CurrentTargetFramework)]
@@ -30,6 +29,54 @@ public void GenerateBuildRuntimeConfigurationFiles_runs_incrementally(string tar
runtimeConfigDevJsonSecondModifiedTime.Should().Be(runtimeConfigDevJsonFirstModifiedTime);
}
+ [TestMethod]
+ public void RuntimeConfigInputCache_changes_when_GenerateProbingPathsToRuntimeConfigDevFile_changes()
+ {
+ var testAsset = TestAssetsManager
+ .CopyTestAsset("HelloWorld", identifier: "ProbingPathsCacheTest")
+ .WithSource()
+ .WithTargetFramework(ToolsetInfo.CurrentTargetFramework);
+
+ var buildCommand = new BuildCommand(testAsset);
+ var intermediateDirectory = buildCommand.GetIntermediateDirectory(ToolsetInfo.CurrentTargetFramework).FullName;
+ var cacheFilePath = Path.Combine(intermediateDirectory, "HelloWorld.genruntimeconfig.cache");
+
+ // Build with default (probing paths disabled for net6.0+)
+ buildCommand.Execute().Should().Pass();
+ var hash1 = File.ReadAllText(cacheFilePath).Trim();
+
+ // Build with probing paths explicitly enabled
+ buildCommand.Execute("/p:GenerateProbingPathsToRuntimeConfigDevFile=true").Should().Pass();
+ var hash2 = File.ReadAllText(cacheFilePath).Trim();
+
+ hash2.Should().NotBe(hash1,
+ "changing GenerateProbingPathsToRuntimeConfigDevFile should change the input cache hash");
+ }
+
+ [TestMethod]
+ public void RuntimeConfigInputCache_changes_when_EnableHotReloadInRuntimeConfigDevFile_changes()
+ {
+ var testAsset = TestAssetsManager
+ .CopyTestAsset("HelloWorld", identifier: "HotReloadCacheTest")
+ .WithSource()
+ .WithTargetFramework(ToolsetInfo.CurrentTargetFramework);
+
+ var buildCommand = new BuildCommand(testAsset);
+ var intermediateDirectory = buildCommand.GetIntermediateDirectory(ToolsetInfo.CurrentTargetFramework).FullName;
+ var cacheFilePath = Path.Combine(intermediateDirectory, "HelloWorld.genruntimeconfig.cache");
+
+ // Build with hot reload disabled
+ buildCommand.Execute("/p:EnableHotReloadInRuntimeConfigDevFile=false").Should().Pass();
+ var hash1 = File.ReadAllText(cacheFilePath).Trim();
+
+ // Build with hot reload enabled
+ buildCommand.Execute("/p:EnableHotReloadInRuntimeConfigDevFile=true").Should().Pass();
+ var hash2 = File.ReadAllText(cacheFilePath).Trim();
+
+ hash2.Should().NotBe(hash1,
+ "changing EnableHotReloadInRuntimeConfigDevFile should change the input cache hash");
+ }
+
[TestMethod]
[DataRow("netcoreapp1.1")]
[DataRow(ToolsetInfo.CurrentTargetFramework)]
diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAComServerLibrary.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAComServerLibrary.cs
index 81d2cac48a07..e592a010210a 100644
--- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAComServerLibrary.cs
+++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAComServerLibrary.cs
@@ -32,7 +32,8 @@ public void It_copies_the_comhost_to_the_output_directory()
"ComServer.pdb",
"ComServer.deps.json",
"ComServer.comhost.dll",
- "ComServer.runtimeconfig.json"
+ "ComServer.runtimeconfig.json",
+ "ComServer.runtimeconfig.dev.json"
});
string runtimeConfigFile = Path.Combine(outputDirectory.FullName, "ComServer.runtimeconfig.json");
@@ -70,7 +71,8 @@ public void It_generates_a_regfree_com_manifest_when_requested()
"ComServer.deps.json",
"ComServer.comhost.dll",
"ComServer.X.manifest",
- "ComServer.runtimeconfig.json"
+ "ComServer.runtimeconfig.json",
+ "ComServer.runtimeconfig.dev.json"
});
}
@@ -102,7 +104,8 @@ public void It_embeds_the_clsidmap_in_the_comhost_when_rid_specified(string rid)
"ComServer.pdb",
"ComServer.deps.json",
"ComServer.comhost.dll",
- "ComServer.runtimeconfig.json"
+ "ComServer.runtimeconfig.json",
+ "ComServer.runtimeconfig.dev.json"
});
}
diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildACrossTargetedLibrary.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildACrossTargetedLibrary.cs
index 3088cf760bfd..5ae63c1fa1ab 100644
--- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildACrossTargetedLibrary.cs
+++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildACrossTargetedLibrary.cs
@@ -28,6 +28,7 @@ public void It_builds_nondesktop_library_successfully_on_all_platforms()
$"{ToolsetInfo.CurrentTargetFramework}/NetStandardAndNetCoreApp.dll",
$"{ToolsetInfo.CurrentTargetFramework}/NetStandardAndNetCoreApp.pdb",
$"{ToolsetInfo.CurrentTargetFramework}/NetStandardAndNetCoreApp.runtimeconfig.json",
+ $"{ToolsetInfo.CurrentTargetFramework}/NetStandardAndNetCoreApp.runtimeconfig.dev.json",
$"{ToolsetInfo.CurrentTargetFramework}/NetStandardAndNetCoreApp.deps.json",
$"{ToolsetInfo.CurrentTargetFramework}/Newtonsoft.Json.dll",
$"{ToolsetInfo.CurrentTargetFramework}/NetStandardAndNetCoreApp.deps.json",
diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildANetCoreApp.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildANetCoreApp.cs
index 14e6ff9856e6..68421aff5583 100644
--- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildANetCoreApp.cs
+++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildANetCoreApp.cs
@@ -392,7 +392,9 @@ public void It_stops_generating_runtimeconfig_dev_json_after_net6(string targetF
buildCommand.GetOutputDirectory(targetFramework).FullName,
$"{proj.Name}.runtimeconfig.dev.json");
- buildCommand.Execute().StdOut
+ // Disable Hot Reload runtime options (which are emitted to runtimeconfig.dev.json for net6.0+ in
+ // Debug builds) so this test verifies the probing-paths behavior that was removed after net6.0.
+ buildCommand.Execute("/p:EnableHotReloadInRuntimeConfigDevFile=false").StdOut
.Should()
.NotContain("NETSDK1048");
diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildASolutionWithNonAnyCPUPlatform.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildASolutionWithNonAnyCPUPlatform.cs
index 28c491e530bd..5b597638fc6f 100644
--- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildASolutionWithNonAnyCPUPlatform.cs
+++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildASolutionWithNonAnyCPUPlatform.cs
@@ -25,6 +25,7 @@ public void It_builds_solution_successfully()
.Should()
.OnlyHaveFiles(new[] {
"x64SolutionBuild.runtimeconfig.json",
+ "x64SolutionBuild.runtimeconfig.dev.json",
"x64SolutionBuild.deps.json",
"x64SolutionBuild.dll",
"x64SolutionBuild.pdb",
diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithLibrariesAndRid.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithLibrariesAndRid.cs
index 8dedc4ecdb62..d7e220a8fc81 100644
--- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithLibrariesAndRid.cs
+++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithLibrariesAndRid.cs
@@ -103,6 +103,7 @@ public void It_builds_a_framework_dependent_RID_specific_runnable_output()
"App.pdb",
"App.deps.json",
"App.runtimeconfig.json",
+ "App.runtimeconfig.dev.json",
"LibraryWithoutRid.dll",
"LibraryWithoutRid.pdb",
"LibraryWithRid.dll",
diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithLibrary.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithLibrary.cs
index 823ab9ea261e..4750683038f9 100644
--- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithLibrary.cs
+++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithLibrary.cs
@@ -50,6 +50,7 @@ void VerifyAppBuilds(TestAsset testAsset)
$"TestApp{EnvironmentInfo.ExecutableExtension}",
"TestApp.deps.json",
"TestApp.runtimeconfig.json",
+ "TestApp.runtimeconfig.dev.json",
"TestLibrary.dll",
"TestLibrary.pdb",
});
@@ -146,6 +147,7 @@ public void The_clean_target_removes_all_files_from_the_output_folder()
$"TestApp{EnvironmentInfo.ExecutableExtension}",
"TestApp.deps.json",
"TestApp.runtimeconfig.json",
+ "TestApp.runtimeconfig.dev.json",
"TestLibrary.dll",
"TestLibrary.pdb"
});
diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithTransitiveNonSdkProjectRefs.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithTransitiveNonSdkProjectRefs.cs
index 81a0a45ee5d2..24e3fb9670f7 100644
--- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithTransitiveNonSdkProjectRefs.cs
+++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithTransitiveNonSdkProjectRefs.cs
@@ -172,6 +172,7 @@ private string VerifyAppBuilds(TestAsset testAsset, string prefix)
$"TestApp{EnvironmentInfo.ExecutableExtension}",
"TestApp.deps.json",
"TestApp.runtimeconfig.json",
+ "TestApp.runtimeconfig.dev.json",
prefix + "MainLibrary.dll",
prefix + "MainLibrary.pdb",
prefix + "AuxLibrary.dll",
diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithTransitiveProjectRefs.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithTransitiveProjectRefs.cs
index 2e47c5b9c1ae..8a77b84a28de 100644
--- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithTransitiveProjectRefs.cs
+++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithTransitiveProjectRefs.cs
@@ -37,6 +37,7 @@ void VerifyAppBuilds(TestAsset testAsset)
$"TestApp{EnvironmentInfo.ExecutableExtension}",
"TestApp.deps.json",
"TestApp.runtimeconfig.json",
+ "TestApp.runtimeconfig.dev.json",
"MainLibrary.dll",
"MainLibrary.pdb",
"AuxLibrary.dll",
@@ -76,6 +77,7 @@ public void The_clean_target_removes_all_files_from_the_output_folder()
$"TestApp{EnvironmentInfo.ExecutableExtension}",
"TestApp.deps.json",
"TestApp.runtimeconfig.json",
+ "TestApp.runtimeconfig.dev.json",
"MainLibrary.dll",
"MainLibrary.pdb",
"AuxLibrary.dll",
diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithoutTransitiveProjectRefs.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithoutTransitiveProjectRefs.cs
index 7be4397a93dc..cf29d4d3e419 100644
--- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithoutTransitiveProjectRefs.cs
+++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAnAppWithoutTransitiveProjectRefs.cs
@@ -75,6 +75,7 @@ public void It_cleans_the_project_successfully_with_static_graph_and_isolation()
"1.pdb",
"1.deps.json",
"1.runtimeconfig.json",
+ "1.runtimeconfig.dev.json",
$"1{EnvironmentInfo.ExecutableExtension}"
};
@@ -164,6 +165,7 @@ public void It_builds_the_project_successfully_when_RAR_does_not_find_all_refere
"1.pdb",
"1.deps.json",
"1.runtimeconfig.json",
+ "1.runtimeconfig.dev.json",
"2.dll",
"2.pdb",
$"1{EnvironmentInfo.ExecutableExtension}",
diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToCopyLocalDependencies.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToCopyLocalDependencies.cs
index d0b7d8957add..bb3c30883485 100644
--- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToCopyLocalDependencies.cs
+++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToCopyLocalDependencies.cs
@@ -42,6 +42,7 @@ public void It_copies_local_package_dependencies_on_build()
$"{ProjectName}.dll",
$"{ProjectName}.pdb",
$"{ProjectName}.runtimeconfig.json",
+ $"{ProjectName}.runtimeconfig.dev.json",
"Newtonsoft.Json.dll",
"runtimes/linux-x64/native/libsqlite3.so",
"runtimes/osx-x64/native/libsqlite3.dylib",
@@ -82,6 +83,7 @@ public void It_does_not_copy_local_package_dependencies_when_requested_not_to()
$"{ProjectName}.dll",
$"{ProjectName}.pdb",
$"{ProjectName}.runtimeconfig.json",
+ $"{ProjectName}.runtimeconfig.dev.json",
});
}
@@ -121,6 +123,7 @@ public void It_copies_local_specific_runtime_package_dependencies_on_build()
$"{ProjectName}.dll",
$"{ProjectName}.pdb",
$"{ProjectName}.runtimeconfig.json",
+ $"{ProjectName}.runtimeconfig.dev.json",
"Newtonsoft.Json.dll",
// NOTE: this may break in the future when the SDK supports platforms that libuv does not
$"libuv{FileConstants.DynamicLibSuffix}"
diff --git a/test/Microsoft.NET.Pack.Tests/GivenThatWeWantToPackACrossTargetedLibrary.cs b/test/Microsoft.NET.Pack.Tests/GivenThatWeWantToPackACrossTargetedLibrary.cs
index 9f58ed1d28fe..721f2be23e08 100644
--- a/test/Microsoft.NET.Pack.Tests/GivenThatWeWantToPackACrossTargetedLibrary.cs
+++ b/test/Microsoft.NET.Pack.Tests/GivenThatWeWantToPackACrossTargetedLibrary.cs
@@ -28,6 +28,7 @@ public void It_packs_nondesktop_library_successfully_on_all_platforms()
$"{ToolsetInfo.CurrentTargetFramework}/NetStandardAndNetCoreApp.dll",
$"{ToolsetInfo.CurrentTargetFramework}/NetStandardAndNetCoreApp.pdb",
$"{ToolsetInfo.CurrentTargetFramework}/NetStandardAndNetCoreApp.runtimeconfig.json",
+ $"{ToolsetInfo.CurrentTargetFramework}/NetStandardAndNetCoreApp.runtimeconfig.dev.json",
$"{ToolsetInfo.CurrentTargetFramework}/NetStandardAndNetCoreApp.deps.json",
$"{ToolsetInfo.CurrentTargetFramework}/Newtonsoft.Json.dll",
$"{ToolsetInfo.CurrentTargetFramework}/NetStandardAndNetCoreApp{EnvironmentInfo.ExecutableExtension}",
diff --git a/test/Microsoft.NET.Pack.Tests/GivenThatWeWantToPackASimpleLibrary.cs b/test/Microsoft.NET.Pack.Tests/GivenThatWeWantToPackASimpleLibrary.cs
index 420c666499a1..b1bd08474fa2 100644
--- a/test/Microsoft.NET.Pack.Tests/GivenThatWeWantToPackASimpleLibrary.cs
+++ b/test/Microsoft.NET.Pack.Tests/GivenThatWeWantToPackASimpleLibrary.cs
@@ -34,6 +34,7 @@ public void It_packs_successfully()
$"HelloWorld.pdb",
$"HelloWorld.deps.json",
$"HelloWorld.runtimeconfig.json",
+ $"HelloWorld.runtimeconfig.dev.json",
$"HelloWorld{EnvironmentInfo.ExecutableExtension}",
});
}
diff --git a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/WasmBuildIncrementalismTest.cs b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/WasmBuildIncrementalismTest.cs
index fb52fcfc8117..ceb814565e01 100644
--- a/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/WasmBuildIncrementalismTest.cs
+++ b/test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/WasmBuildIncrementalismTest.cs
@@ -31,6 +31,7 @@ public void Build_IsIncremental()
var filesToIgnore = new[]
{
Path.Combine(buildOutputDirectory, "blazorwasm.runtimeconfig.json"),
+ Path.Combine(buildOutputDirectory, "blazorwasm.runtimeconfig.dev.json"),
Path.Combine(buildOutputDirectory, "RazorClassLibrary.staticwebassets.endpoints.json"),
Path.Combine(buildOutputDirectory, "blazorwasm.staticwebassets.endpoints.json")
};
diff --git a/test/dotnet-watch.Tests/HotReload/SourceFileUpdateTests.HotReloadNotSupported.cs b/test/dotnet-watch.Tests/HotReload/SourceFileUpdateTests.HotReloadNotSupported.cs
index fc621eb004d2..aa2404a9d060 100644
--- a/test/dotnet-watch.Tests/HotReload/SourceFileUpdateTests.HotReloadNotSupported.cs
+++ b/test/dotnet-watch.Tests/HotReload/SourceFileUpdateTests.HotReloadNotSupported.cs
@@ -3,6 +3,8 @@
#nullable disable
+using Combinatorial.MSTest;
+
namespace Microsoft.DotNet.Watch.UnitTests;
[TestClass]
@@ -13,22 +15,14 @@ public class SourceFileUpdateTests_HotReloadNotSupported : DotNetWatchTestBase
[DataRow("PublishTrimmed", "True")]
[DataRow("StartupHookSupport", "False")]
[DataRow("Optimize", "True")]
- [DataRow("MetadataUpdaterSupport", "False")]
- public async Task ChangeFileInAotProject(string propertyName, string propertyValue)
+ public async Task ChangeFileInAotProject_PriorNet11(string propertyName, string propertyValue)
{
- var tfvParsed = Version.Parse(ToolsetInfo.CurrentTargetFrameworkVersion);
- var isNet11OrNewer = tfvParsed.Major >= 11;
-
- // Optimize check only applies to < .NET 11; MetadataUpdaterSupport only to >= .NET 11.
- if (propertyName == "Optimize" && isNet11OrNewer)
- return;
- if (propertyName == "MetadataUpdaterSupport" && !isNet11OrNewer)
- return;
-
- var projectDisplay = $"WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})";
+ var tfm = "net9.0";
+ var projectDisplay = $"WatchHotReloadApp ({tfm})";
var testAsset = TestAssets.CopyTestAsset("WatchHotReloadApp", identifier: $"{propertyName};{propertyValue}")
.WithSource()
+ .WithTargetFramework(tfm)
.WithProjectChanges(project =>
{
project.Root.Descendants()
@@ -44,9 +38,9 @@ public async Task ChangeFileInAotProject(string propertyName, string propertyVal
var (suggestedProperty, suggestedValue) = propertyName switch
{
"Optimize" => (PropertyNames.Optimize, "False"),
- "MetadataUpdaterSupport" => (PropertyNames.MetadataUpdaterSupport, "True"),
_ => (PropertyNames.StartupHookSupport, "True"),
};
+
var message = MessageDescriptor.ProjectDoesNotSupportHotReload_Property.GetMessage((propertyName, propertyValue, suggestedProperty, suggestedValue));
await App.WaitForOutputLineContaining($"[{projectDisplay}] {message}");
await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges);
@@ -58,6 +52,76 @@ public async Task ChangeFileInAotProject(string propertyName, string propertyVal
await App.WaitForOutputLineContaining("");
}
+ [TestMethod]
+ [CombinatorialData]
+ public async Task ChangeFileInAotProject_Net11_DisabledInConfigDevFile(bool startupHookSupport)
+ {
+ var tfm = ToolsetInfo.CurrentTargetFramework;
+ var projectDisplay = $"WatchHotReloadApp ({tfm})";
+
+ var testAsset = TestAssets.CopyTestAsset("WatchHotReloadApp", identifier: $"{startupHookSupport}")
+ .WithSource()
+ .WithTargetFramework(tfm)
+ .WithProjectChanges(project =>
+ {
+ project.Root.Descendants()
+ .First(e => e.Name.LocalName == "PropertyGroup")
+ .Add(
+ XElement.Parse($"false"),
+ XElement.Parse($"{!startupHookSupport}"),
+ XElement.Parse($"{startupHookSupport}"));
+ });
+
+ var programPath = Path.Combine(testAsset.Path, "Program.cs");
+
+ App.Start(testAsset, ["--non-interactive"]);
+
+ var propertyName = startupHookSupport ? "MetadataUpdaterSupport" : "StartupHookSupport";
+ var message = MessageDescriptor.ProjectDoesNotSupportHotReload_Property.GetMessage((propertyName, "False", "EnableHotReloadInRuntimeConfigDevFile", "True"));
+ await App.WaitForOutputLineContaining($"[{projectDisplay}] {message}");
+ await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges);
+ App.Process.ClearOutput();
+
+ UpdateSourceFile(programPath, content => content.Replace("Console.WriteLine(\".\");", "Console.WriteLine(\"\");"));
+
+ await App.WaitForOutputLineContaining($"[{projectDisplay}] [auto-restart] {programPath}(1,1): error ENC0097"); // Applying source changes while the application is running is not supported by the runtime.
+ await App.WaitForOutputLineContaining("");
+ }
+
+ [TestMethod]
+ [CombinatorialData]
+ public async Task ChangeFileInAotProject_Net11_EnabledInConfigFile(bool enabledInDevFile)
+ {
+ var tfm = ToolsetInfo.CurrentTargetFramework;
+ var projectDisplay = $"WatchHotReloadApp ({tfm})";
+
+ var testAsset = TestAssets.CopyTestAsset("WatchHotReloadApp", identifier: $"{enabledInDevFile}")
+ .WithSource()
+ .WithTargetFramework(tfm)
+ .WithProjectChanges(project =>
+ {
+ project.Root.Descendants()
+ .First(e => e.Name.LocalName == "PropertyGroup")
+ .Add(
+ XElement.Parse($"{enabledInDevFile}"),
+ XElement.Parse($"{!enabledInDevFile}"),
+ XElement.Parse($"{!enabledInDevFile}"));
+ });
+
+ var programPath = Path.Combine(testAsset.Path, "Program.cs");
+
+ App.Start(testAsset, ["--non-interactive"]);
+
+ await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges);
+ App.AssertOutputDoesNotContain("⚠");
+ App.Process.ClearOutput();
+
+ UpdateSourceFile(programPath, content => content.Replace("Console.WriteLine(\".\");", "Console.WriteLine(\"\");"));
+
+ await App.WaitForOutputLineContaining(MessageDescriptor.ManagedCodeChangesApplied);
+ await App.WaitForOutputLineContaining("");
+ }
+
[TestMethod]
public async Task ChangeFileInFSharpProject()
{