Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 42 additions & 26 deletions src/Dotnet.Watch/Watch/AppModels/HotReloadAppModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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);
}
}
1 change: 1 addition & 0 deletions src/Dotnet.Watch/Watch/Build/BuildNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,18 @@ public class GenerateRuntimeConfigurationFiles : TaskBase, IMultiThreadableTask

public bool WriteIncludedFrameworks { get; set; }

public bool GenerateRuntimeConfigDevFile { get; set; }
/// <summary>
/// True to generate probing paths to runtimeconfig.dev.json file.
/// </summary>
public bool GenerateProbingPathsToRuntimeConfigDevFile { get; set; }

/// <summary>
/// True to generate switches that enable Hot Reload to runtimeconfig.dev.json file.
/// </summary>
public bool GenerateHotReloadRuntimeOptionsToRuntimeConfigDevFile { get; set; }

private bool GenerateRuntimeConfigDevFile =>
GenerateProbingPathsToRuntimeConfigDevFile || GenerateHotReloadRuntimeOptionsToRuntimeConfigDevFile;

public bool AlwaysIncludeCoreFramework { get; set; }

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -342,7 +353,17 @@ private void WriteDevRuntimeConfig(IList<LockFileItem> 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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,26 @@ Copyright (c) .NET Foundation. All rights reserved.
<ProjectConfigurationDescription Include="TargetFramework=$(TargetFramework)" />
</ItemGroup>

<!-- Compatibility: if GenerateRuntimeConfigDevFile was set to true prior to adding GenerateProbingPathsToRuntimeConfigDevFile switch, the additional probing paths were generated -->
<PropertyGroup Condition="'$(GenerateRuntimeConfigDevFile)' == 'true'">
<GenerateProbingPathsToRuntimeConfigDevFile Condition="'$(GenerateProbingPathsToRuntimeConfigDevFile)' == ''">true</GenerateProbingPathsToRuntimeConfigDevFile>
</PropertyGroup>

<PropertyGroup Condition="'$(GenerateRuntimeConfigDevFile)' == ''">
<GenerateRuntimeConfigDevFile>true</GenerateRuntimeConfigDevFile>
<!-- Post-net6.0, stop generating *.runtimeconfig.dev.json files to reduce probing paths. -->
<!-- https://github.com/dotnet/sdk/issues/16818 -->
<GenerateRuntimeConfigDevFile Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' and $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '6.0'))">false</GenerateRuntimeConfigDevFile>
<GenerateProbingPathsToRuntimeConfigDevFile Condition="'$(GenerateProbingPathsToRuntimeConfigDevFile)' == '' and ('$(TargetFrameworkIdentifier)' != '.NETCoreApp' or $([MSBuild]::VersionLessThan($(TargetFrameworkVersion), '6.0')))">true</GenerateProbingPathsToRuntimeConfigDevFile>
<EnableHotReloadInRuntimeConfigDevFile Condition="'$(EnableHotReloadInRuntimeConfigDevFile)' == '' and '$(Configuration)' == 'Debug'">true</EnableHotReloadInRuntimeConfigDevFile>

<GenerateRuntimeConfigDevFile Condition="'$(GenerateProbingPathsToRuntimeConfigDevFile)' == 'true' or '$(EnableHotReloadInRuntimeConfigDevFile)' == 'true'">true</GenerateRuntimeConfigDevFile>
Comment thread
tmat marked this conversation as resolved.
</PropertyGroup>

<PropertyGroup>
<ProjectDepsFileName Condition="'$(ProjectDepsFileName)' == ''">$(AssemblyName).deps.json</ProjectDepsFileName>
<ProjectDepsFilePath Condition="'$(ProjectDepsFilePath)' == ''">$(TargetDir)$(ProjectDepsFileName)</ProjectDepsFilePath>
<ProjectRuntimeConfigFileName Condition="'$(ProjectRuntimeConfigFileName)' == ''">$(AssemblyName).runtimeconfig.json</ProjectRuntimeConfigFileName>
<ProjectRuntimeConfigFilePath Condition="'$(ProjectRuntimeConfigFilePath)' == ''">$(TargetDir)$(ProjectRuntimeConfigFileName)</ProjectRuntimeConfigFilePath>
<ProjectRuntimeConfigDevFilePath Condition="'$(ProjectRuntimeConfigDevFilePath)' == '' and $(GenerateRuntimeConfigDevFile) == 'true'">$(TargetDir)$(AssemblyName).runtimeconfig.dev.json</ProjectRuntimeConfigDevFilePath>
<ProjectRuntimeConfigDevFilePath Condition="'$(ProjectRuntimeConfigDevFilePath)' == '' and '$(GenerateRuntimeConfigDevFile)' == 'true'">$(TargetDir)$(AssemblyName).runtimeconfig.dev.json</ProjectRuntimeConfigDevFilePath>
<IncludeMainProjectInDepsFile Condition=" '$(IncludeMainProjectInDepsFile)' == '' ">true</IncludeMainProjectInDepsFile>
<TrimDepsJsonLibrariesWithoutAssets Condition=" '$(TrimDepsJsonLibrariesWithoutAssets)' == '' ">true</TrimDepsJsonLibrariesWithoutAssets>
</PropertyGroup>
Expand Down Expand Up @@ -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)"/>
</ItemGroup>

Expand Down Expand Up @@ -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)">
Comment thread
tmat marked this conversation as resolved.

</GenerateRuntimeConfigurationFiles>
Expand Down
12 changes: 6 additions & 6 deletions test/Microsoft.DotNet.HotReload.Test.Utilities/WatchableApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,31 +119,31 @@ public async ValueTask<string> WaitUntilOutputContains(MessageDescriptor descrip
return matchingLine;
}

public Task<string> WaitForOutputLineContaining(string text, [CallerFilePath] string? testPath = null, [CallerLineNumber] int testLine = 0)
public async Task<string> 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<string> WaitForOutputLineContaining(MessageDescriptor descriptor, string? projectDisplay = null, [CallerLineNumber] int testLine = 0, [CallerFilePath] string? testPath = null)
public async Task<string> 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<string> WaitForOutputLineContaining(Regex pattern, [CallerFilePath] string? testPath = null, [CallerLineNumber] int testLine = 0)
public async Task<string> 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;
Expand Down
Loading
Loading