Skip to content

Commit 5dc7df4

Browse files
authored
[dotnet] Make 'dotnet watch' work. (#24922)
* Use web sockets to connect the endpoints when doing hot reload / dotnet watch. * Change the defaults when launching desktop apps and we're in 'dotnet watch' mode: * Always open app in a new instance. * Wait for exit enabled. * Pipe stdout/stderr to the current terminal. * Validate that the project wasn't built with a build configuration that would prevent Hot Reload from working. Contributes towards #24782.
1 parent 4a99c2e commit 5dc7df4

18 files changed

+396
-1
lines changed

dotnet/targets/Microsoft.Sdk.Desktop.targets

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,38 @@
33
<Target
44
Name="_PrepareRunDesktop"
55
BeforeTargets="ComputeRunArguments"
6+
DependsOnTargets="_ValidateHotReloadConfiguration"
67
Condition="'$(_PlatformName)' == 'macOS' Or '$(_PlatformName)' == 'MacCatalyst'">
78

9+
<!--
10+
If we're running under 'dotnet watch', change a few defaults to:
11+
* Open in a new instance: enabled
12+
* Wait for exit: enabled
13+
* stdout/stderr: send to the current terminal
14+
-->
15+
<ItemGroup>
16+
<_DotNetWatchVariable Include="@(RuntimeEnvironmentVariable)" Condition="'%(Identity)' == 'DOTNET_WATCH' And '%(Value)' == '1'" />
17+
</ItemGroup>
18+
<PropertyGroup Condition="'$(RunWithOpen)' != 'false' And '@(_DotNetWatchVariable->Count())' == '1'">
19+
<_IsDotNetWatch>true</_IsDotNetWatch>
20+
<OpenNewInstance Condition="'$(OpenNewInstance)' == ''">true</OpenNewInstance>
21+
<OpenWaitForExit Condition="'$(OpenWaitForExit)' == ''">true</OpenWaitForExit>
22+
</PropertyGroup>
23+
<Exec
24+
Command="tty"
25+
Condition="'$(StandardOutputPath)' == '' Or '$(StandardErrorPath)' == ''"
26+
ConsoleToMSBuild="true"
27+
IgnoreStandardErrorWarningFormat="true"
28+
IgnoreExitCode="true"
29+
>
30+
<Output TaskParameter="ConsoleOutput" ItemName="_TtyOutput" />
31+
</Exec>
32+
<PropertyGroup Condition="'$(_IsDotNetWatch)' == 'true'">
33+
<_TtyPath>@(_TtyOutput)</_TtyPath>
34+
<StandardOutputPath Condition="'$(StandardOutputPath)' == '' And Exists('$(_TtyPath)')">$(_TtyPath)</StandardOutputPath>
35+
<StandardErrorPath Condition="'$(StandardErrorPath)' == '' And Exists('$(_TtyPath)')">$(_TtyPath)</StandardErrorPath>
36+
</PropertyGroup>
37+
838
<PropertyGroup Condition="'$(RunWithOpen)' != 'false'">
939
<_OpenArguments Condition="'$(XamarinDebugMode)' != ''">$(_OpenArguments) --env __XAMARIN_DEBUG_MODE__=$(XamarinDebugMode)</_OpenArguments>
1040
<_OpenArguments Condition="'$(XamarinDebugPort)' != ''">$(_OpenArguments) --env __XAMARIN_DEBUG_PORT__=$(XamarinDebugPort)</_OpenArguments>

dotnet/targets/Microsoft.Sdk.Mobile.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@
138138
<Target
139139
Name="_PrepareRunMobile"
140140
BeforeTargets="ComputeRunArguments"
141-
DependsOnTargets="_InstallMobile;ComputeMlaunchRunArguments"
141+
DependsOnTargets="_ValidateHotReloadConfiguration;_InstallMobile;ComputeMlaunchRunArguments"
142142
Condition="'$(_PlatformName)' != 'macOS' And '$(_PlatformName)' != 'MacCatalyst'">
143143
</Target>
144144

dotnet/targets/Xamarin.Shared.Sdk.targets

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@
6464
<ProjectCapability Include="Apple" />
6565
<ProjectCapability Include="Mobile" />
6666
<ProjectCapability Include="RuntimeEnvironmentVariableSupport" />
67+
<!-- We have to use a network connection (web sockets) to connect hot reload / dotnet watch when running on device, so enable that. Since this also works locally (simulator/desktop), just enable it always -->
68+
<ProjectCapability Include="HotReloadWebSockets" />
6769

6870
<ProjectCapability Include="IOSApplication" Condition="'$(_ProjectType)' == 'iOSExecutableProject'" />
6971
<ProjectCapability Include="IOSAppExtension" Condition="'$(_ProjectType)' == 'iOSAppExtensionProject'" />
@@ -2562,6 +2564,28 @@ global using nfloat = global::System.Runtime.InteropServices.NFloat%3B
25622564
</ItemGroup>
25632565
</Target>
25642566

2567+
<!--
2568+
Validate that the build configuration will work for Hot Reload.
2569+
This target is executed when doing 'dotnet run'
2570+
-->
2571+
<Target Name="_ValidateHotReloadConfiguration">
2572+
<ItemGroup>
2573+
<_HotReloadVariable Include="@(RuntimeEnvironmentVariable)" Condition="'%(Identity)' == 'DOTNET_WATCH' And '%(Value)' == '1'" />
2574+
</ItemGroup>
2575+
<PropertyGroup Condition="@(_HotReloadVariable->Count()) &gt; 0">
2576+
<_IsHotReloadLaunch>true</_IsHotReloadLaunch>
2577+
</PropertyGroup>
2578+
2579+
<Error
2580+
Condition="'$(_IsHotReloadLaunch)' == 'true' And '$(UseMonoRuntime)' == 'true' And '$(MtouchInterpreter)' == ''"
2581+
Text="Can't use Hot Reload or 'dotnet watch' unless the interpreter is enabled. Set 'UseInterpreter=true' in the project file to use the interpreter."
2582+
/>
2583+
<Error
2584+
Condition="'$(_IsHotReloadLaunch)' == 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true'"
2585+
Text="Can't use Hot Reload or 'dotnet watch' if any assemblies are trimmed. Set 'MtouchLink=None' in the project file to disable trimming."
2586+
/>
2587+
</Target>
2588+
25652589
<!-- Import existing targets -->
25662590

25672591
<!-- Make sure post-processing only includes dynamic frameworks that will actually be in the app bundle. Ref: https://github.com/dotnet/macios/issues/24840 -->
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.IO;
3+
using System.Runtime.InteropServices;
4+
using System.Threading;
5+
6+
using Foundation;
7+
8+
#nullable enable
9+
10+
namespace HotReloadTestApp;
11+
12+
public partial class Program {
13+
14+
static string Variable = "Variable has not changed";
15+
static bool ContinueLooping = true;
16+
17+
static partial void ChangeVariable ();
18+
19+
20+
static int Main (string [] args)
21+
{
22+
GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly
23+
24+
Print (0);
25+
26+
for (var i = 0; i < 120 && ContinueLooping; i++) {
27+
DoSomething (i + 1);
28+
Thread.Sleep (TimeSpan.FromSeconds (1));
29+
}
30+
31+
return ContinueLooping ? 1 : 0;
32+
}
33+
34+
static void DoSomething (int i)
35+
{
36+
ChangeVariable ();
37+
Print (i);
38+
}
39+
40+
static string? LogPath = Environment.GetEnvironmentVariable ("HOTRELOAD_TEST_APP_LOGFILE");
41+
static StreamWriter? logStream;
42+
static void Print (int number)
43+
{
44+
var msg = $"{number} Variable={Variable}";
45+
if (!string.IsNullOrEmpty (LogPath)) {
46+
if (logStream is null) {
47+
var fs = new FileStream (LogPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
48+
logStream = new StreamWriter (fs);
49+
logStream.AutoFlush = true;
50+
}
51+
logStream.WriteLine (msg);
52+
}
53+
Console.WriteLine (msg);
54+
}
55+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project Sdk="Microsoft.NET.Sdk">
3+
<PropertyGroup>
4+
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-maccatalyst</TargetFramework>
5+
</PropertyGroup>
6+
<Import Project="..\shared.csproj" />
7+
</Project>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>LSUIElement</key>
6+
<string>1</string>
7+
</dict>
8+
</plist>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include ../shared.mk
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
TOP=../../..
2+
include $(TOP)/tests/common/shared-dotnet-test.mk
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This is a test app for 'dotnet watch' (not for watchOS).
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project Sdk="Microsoft.NET.Sdk">
3+
<PropertyGroup>
4+
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-ios</TargetFramework>
5+
</PropertyGroup>
6+
<Import Project="..\shared.csproj" />
7+
</Project>

0 commit comments

Comments
 (0)