Skip to content

Commit 08e21da

Browse files
[main] Source code updates from dotnet/sdk (#5402)
[main] Source code updates from dotnet/sdk - Resolve codeflow conflicts for SDK forward flow Accept SDK source versions for Dotnet.Watch files and fix duplicate Debug.Assert in EvaluationResult.cs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> - Remove UTF-8 BOM from source-build.slnf The BOM was introduced during conflict resolution and causes MSBuild parse failures on Linux (MSB4025: Data at the root level is invalid). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> - Update source-build.slnf - Fix wrong path in .slnf
1 parent 41b5534 commit 08e21da

File tree

652 files changed

+31219
-20232
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

652 files changed

+31219
-20232
lines changed

src/sdk/.editorconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ file_header_template = Licensed to the .NET Foundation under one or more agreeme
173173
dotnet_code_quality.ca1802.api_surface = private, internal
174174
dotnet_code_quality.ca1822.api_surface = private, internal
175175
dotnet_code_quality.ca2208.api_surface = public
176+
dotnet_public_api_analyzer.require_api_files = true
176177
# Mark attributes with AttributeUsageAttribute
177178
dotnet_diagnostic.CA1018.severity = warning
178179
# Properties should not be write only

src/sdk/.github/copilot-instructions.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ Coding Style and Changes:
66
- Code should match the style of the file it's in.
77
- Changes should be minimal to resolve a problem in a clean way.
88
- User-visible changes to behavior should be considered carefully before committing. They should always be flagged.
9-
- When generating code, run `dotnet format` to ensure uniform formatting.
9+
- Only edit the files that are necessary to address the specific issue. Do not run `dotnet format` or make formatting changes to additional files.
1010
- Prefer using file-based namespaces for new code.
1111
- Do not allow unused `using` directives to be committed.
12-
- Commit your changes, and then format them.
13-
- Add the format commit SHA to the .git-blame-ignore-revs file so that the commit doesn't dirty git blame in the future
1412
- Use `#if NET` blocks for .NET Core specific code, and `#if NETFRAMEWORK` for .NET Framework specific code.
1513

1614
Testing:

src/sdk/.github/workflows/backport.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ jobs:
1313
backport:
1414
uses: dotnet/arcade/.github/workflows/backport-base.yml@main
1515
with:
16+
pr_labels: backport
1617
pr_description_template: |
1718
Backport of #%source_pr_number% to %target_branch%
1819

src/sdk/.vscode/mcp.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"servers": {
3-
"ms.docs": {
3+
"ms-docs": {
44
"url": "https://learn.microsoft.com/api/mcp",
55
"type": "http"
66
}

src/sdk/CODEOWNERS

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# This is a comment.
1+
# This is a comment.
22
# Each line is a file pattern followed by one or more owners.
33
# Syntax: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-syntax
44

@@ -80,9 +80,9 @@
8080
/test/TestAssets/TestProjects/Watch*/ @tmat @dotnet/roslyn-ide
8181
/test/dotnet-watch.Tests/ @tmat @dotnet/roslyn-ide
8282
/test/Microsoft.AspNetCore.Watch.BrowserRefresh.Tests/ @dotnet/aspnet-blazor-eng
83-
/src/BuiltInTools/* @tmat @dotnet/roslyn-ide
84-
/src/BuiltInTools/BrowserRefresh @dotnet/aspnet-blazor-eng
85-
/src/BuiltInTools/AspireService @dotnet/aspnet-blazor-eng
83+
/src/Dotnet.Watch/ @tmat @dotnet/roslyn-ide
84+
/src/Dotnet.Watch/BrowserRefresh @dotnet/aspnet-blazor-eng
85+
/src/Dotnet.Watch/AspireService @dotnet/aspnet-blazor-eng
8686

8787
# Compatibility tools owned by runtime team
8888
/src/Compatibility/ @dotnet/area-infrastructure-libraries

src/sdk/Directory.Build.props

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
<OSName Condition="'$(OSName)' == '' AND $(PortableTargetRid) != ''">$(PortableTargetRid.Substring(0, $(PortableTargetRid.LastIndexOf('-'))))</OSName>
2828
<OSName Condition="'$(OSName)' == '' AND $(TargetRid) != ''">$(TargetRid.Substring(0, $(TargetRid.LastIndexOf('-'))))</OSName>
2929
<OSName Condition="'$(OSName)' == ''">$(HostOSName)</OSName>
30+
31+
<!-- Workaround for https://github.com/NuGet/Home/issues/14745 -->
32+
<SDKAnalysisLevel>10.0.200</SDKAnalysisLevel>
3033
</PropertyGroup>
3134

3235
<PropertyGroup>

src/sdk/cli.slnf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"solution": {
33
"path": "sdk.slnx",
44
"projects": [
5-
"src\\BuiltInTools\\dotnet-watch\\dotnet-watch.csproj",
5+
"src\\Dotnet.Watch\\dotnet-watch\\dotnet-watch.csproj",
66
"src\\Cli\\dotnet\\dotnet.csproj",
77
"src\\Cli\\Microsoft.DotNet.Cli.Utils\\Microsoft.DotNet.Cli.Utils.csproj",
88
"test\\dotnet-new.IntegrationTests\\dotnet-new.IntegrationTests.csproj",

src/sdk/documentation/general/dotnet-run-file.md

Lines changed: 61 additions & 119 deletions
Large diffs are not rendered by default.
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# `dotnet watch` for .NET MAUI Scenarios
2+
3+
## Overview
4+
5+
This spec describes how `dotnet watch` provides Hot Reload for mobile platforms (Android, iOS), which cannot use the standard named pipe transport. Similar to how web applications already use websockets for reloading CSS and JavaScript, we will use the same model for mobile applications.
6+
7+
## Transport Selection
8+
9+
| Platform | Transport | Reason |
10+
|-----------------|------------|-------------------------------------------------------------------------------|
11+
| Desktop/Console | Named Pipe | Existing implementation, Fast, local IPC |
12+
| Android/iOS | WebSocket | Named pipes don't work over the network; `adb reverse` tunnels the connection |
13+
14+
`dotnet-watch` detects WebSocket transport via the `HotReloadWebSockets` capability:
15+
16+
```xml
17+
<ProjectCapability Include="HotReloadWebSockets" />
18+
```
19+
20+
Mobile workloads (Android, iOS) add this capability to their SDK targets. This allows any workload to opt into WebSocket-based hot reload.
21+
22+
## SDK Changes ([dotnet/sdk#52581](https://github.com/dotnet/sdk/pull/52581))
23+
24+
### WebSocket Details
25+
26+
`dotnet-watch` already has a WebSocket server for web apps: `BrowserRefreshServer`. This server:
27+
28+
- Hosts via Kestrel on `https://localhost:<port>`
29+
- Communicates with JavaScript (`aspnetcore-browser-refresh.js`) injected into web pages
30+
- Sends commands like "refresh CSS", "reload page", "apply Blazor delta"
31+
32+
For mobile, we reuse the Kestrel infrastructure but with a different protocol:
33+
34+
| Server | Client | Protocol |
35+
|----------------------------|------------------------|--------------------------------------------|
36+
| `BrowserRefreshServer` | JavaScript in browser | JSON messages for CSS/page refresh |
37+
| `WebSocketClientTransport` | Startup hook on device | Binary delta payloads (same as named pipe) |
38+
39+
The mobile transport (`WebSocketClientTransport`) composes a sealed `KestrelWebSocketServer` and speaks the same binary protocol as the named pipe transport, just over WebSocket instead.
40+
41+
### WebSocket Authentication
42+
43+
To prevent unauthorized processes from connecting to the hot reload server, `WebSocketClientTransport` uses RSA-based authentication identical to `BrowserRefreshServer`:
44+
45+
1. **Server generates RSA key pair:** `SharedSecretProvider` creates a 2048-bit RSA key on startup
46+
2. **Public key exported:** The public key (X.509 SubjectPublicKeyInfo, Base64-encoded) is passed to the app via `DOTNET_WATCH_HOTRELOAD_WEBSOCKET_KEY`
47+
3. **Client encrypts secret:** The startup hook generates a random 32-byte secret, encrypts it with RSA-OAEP-SHA256 using the public key
48+
4. **Secret sent as subprotocol:** The encrypted secret is Base64-encoded (URL-safe: `-` for `+`, `_` for `/`, no padding) and sent as the WebSocket subprotocol header
49+
5. **Server validates:** `WebSocketClientTransport.HandleRequestAsync` decrypts the subprotocol value and accepts the connection only if decryption succeeds
50+
51+
This ensures only processes that received the public key via the environment variable can connect. The URL-safe Base64 encoding is required because WebSocket subprotocol tokens cannot contain `+`, `/`, or `=` characters.
52+
53+
**Environment variables:**
54+
- `DOTNET_WATCH_HOTRELOAD_WEBSOCKET_ENDPOINT` — WebSocket URL (e.g., `ws://127.0.0.1:5432`)
55+
- `DOTNET_WATCH_HOTRELOAD_WEBSOCKET_KEY` — RSA public key (Base64-encoded)
56+
57+
### 1. WebSocket Capability Detection
58+
59+
[ProjectGraphUtilities.cs](../../src/Dotnet.Watch/Watch/Build/ProjectGraphUtilities.cs) checks for the `HotReloadWebSockets` capability.
60+
61+
### 2. MobileAppModel
62+
63+
Creates a `DefaultHotReloadClient` with a `WebSocketClientTransport` instead of the default `NamedPipeClientTransport`.
64+
65+
### 3. Environment Variables
66+
67+
`dotnet-watch` launches the app via:
68+
69+
```dotnetcli
70+
dotnet run --no-build \
71+
-e DOTNET_WATCH=1 \
72+
-e DOTNET_MODIFIABLE_ASSEMBLIES=debug \
73+
-e DOTNET_WATCH_HOTRELOAD_WEBSOCKET_ENDPOINT=ws://127.0.0.1:<port> \
74+
-e DOTNET_WATCH_HOTRELOAD_WEBSOCKET_KEY=<base64-encoded-rsa-public-key> \
75+
-e DOTNET_STARTUP_HOOKS=<path to DeltaApplier.dll>
76+
```
77+
78+
The port is dynamically assigned (defaults to 0, meaning the OS picks an available port) to avoid conflicts in CI and parallel test scenarios. The `DOTNET_WATCH_AGENT_WEBSOCKET_PORT` environment variable can override this if a specific port is needed.
79+
80+
These environment variables are passed as `@(RuntimeEnvironmentVariable)` MSBuild items to the workload. See [dotnet-run-for-maui.md](dotnet-run-for-maui.md) for details on `dotnet run` and environment variables.
81+
82+
## Android Workload Changes (Example Integration)
83+
84+
### [dotnet/android#10770](https://github.com/dotnet/android/pull/10770) — RuntimeEnvironmentVariable Support
85+
86+
Enables the Android workload to receive env vars from `dotnet run -e`:
87+
88+
- Adds `<ProjectCapability Include="RuntimeEnvironmentVariableSupport" />`
89+
- Adds `<ProjectCapability Include="HotReloadWebSockets" />` to opt into WebSocket-based hot reload
90+
- Configures `@(RuntimeEnvironmentVariable)` items, so they will apply to Android.
91+
92+
### [dotnet/android#10778](https://github.com/dotnet/android/pull/10778) — dotnet-watch Integration
93+
94+
1. **Startup Hook:** Parses `DOTNET_STARTUP_HOOKS`, includes the assembly in the app package, rewrites the path to just the assembly name (since the full path doesn't exist on device)
95+
2. **Port Forwarding:** Runs `adb reverse tcp:<port> tcp:<port>` so the device can reach the host's WebSocket server via `127.0.0.1:<port>` (port is parsed from the endpoint URL)
96+
3. **Prevents Double Connection:** Disables startup hooks in `Microsoft.Android.Run` (the desktop launcher) so only the mobile app connects
97+
98+
## Data Flow
99+
100+
1. **Build:** `dotnet-watch` builds the project, detects `HotReloadWebSockets` capability
101+
2. **Launch:** `dotnet run -e DOTNET_WATCH_HOTRELOAD_WEBSOCKET_ENDPOINT=ws://127.0.0.1:<port> -e DOTNET_WATCH_HOTRELOAD_WEBSOCKET_KEY=<key> -e DOTNET_STARTUP_HOOKS=...`
102+
3. **Workload:** Android build tasks:
103+
- Include the startup hook DLL in the APK
104+
- Set up ADB port forwarding for the dynamically assigned port
105+
- Rewrite env vars for on-device paths
106+
4. **Device:** App starts → StartupHook loads → `Transport.TryCreate()` reads env vars → `WebSocketTransport` encrypts secret with RSA public key → connects to `ws://127.0.0.1:<port>` with encrypted secret as subprotocol
107+
5. **Server:** `WebSocketClientTransport` validates the encrypted secret, accepts connection
108+
6. **Hot Reload:** File change → delta compiled → sent over WebSocket → applied on device
109+
110+
## iOS
111+
112+
Similar changes will be made in the iOS workload to opt into WebSocket-based hot reload:
113+
114+
- Add `<ProjectCapability Include="HotReloadWebSockets" />`
115+
- Handle startup hooks and port forwarding similar to Android
116+
117+
## Dependencies
118+
119+
- **[runtime#123964](https://github.com/dotnet/runtime/pull/123964):** [mono] read `$DOTNET_STARTUP_HOOKS` — needed for Mono runtime to honor startup hooks (temporary workaround via `RuntimeHostConfigurationOption`)

0 commit comments

Comments
 (0)