Skip to content

Commit 6381d24

Browse files
authored
Merge pull request #21424 from unoplatform/dev/dr/udei2
feat(udei): Add dev-server component messages in UDEI
2 parents 8548b8d + c169794 commit 6381d24

File tree

15 files changed

+291
-110
lines changed

15 files changed

+291
-110
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using System;
2+
using System.IO;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
6+
namespace Uno.UI.RemoteControl.Host;
7+
8+
public static class ConsoleHelper
9+
{
10+
public static CancellationTokenSource CreateCancellationToken()
11+
{
12+
// Set up graceful shutdown handling
13+
var cancellationTokenSource = new CancellationTokenSource();
14+
var shutdownRequested = false;
15+
16+
Console.CancelKeyPress += (sender, e) =>
17+
{
18+
if (!cancellationTokenSource.IsCancellationRequested)
19+
{
20+
shutdownRequested = true;
21+
e.Cancel = true; // Prevent immediate termination
22+
Console.WriteLine("Graceful shutdown requested...");
23+
cancellationTokenSource.Cancel();
24+
}
25+
};
26+
27+
// Monitor stdin for CTRL-C character (ASCII 3) for graceful shutdown
28+
_ = Task.Run(async () =>
29+
{
30+
try
31+
{
32+
if (!Console.IsInputRedirected)
33+
return;
34+
35+
using var reader = new StreamReader(Console.OpenStandardInput());
36+
37+
var buffer = new char[1];
38+
while (!cancellationTokenSource.Token.IsCancellationRequested)
39+
{
40+
var read = await reader.ReadAsync(buffer, 0, 1);
41+
if (read == 0)
42+
{
43+
break; // EOF
44+
}
45+
46+
if (buffer[0] != '\x03') // CTRL-C (ASCII 3)
47+
{
48+
continue;
49+
}
50+
51+
if (!shutdownRequested)
52+
{
53+
shutdownRequested = true;
54+
Console.WriteLine("Graceful shutdown requested via stdin...");
55+
await cancellationTokenSource.CancelAsync();
56+
}
57+
58+
break;
59+
}
60+
}
61+
catch (Exception ex)
62+
{
63+
Console.WriteLine($"Error monitoring stdin: {ex.Message}");
64+
}
65+
},
66+
cancellationTokenSource.Token);
67+
return cancellationTokenSource;
68+
}
69+
}

src/Uno.UI.RemoteControl.Host/Extensibility/AddIns.cs

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
using Microsoft.Extensions.Logging;
1010
using Uno.Extensions;
1111
using Uno.UI.RemoteControl.Helpers;
12-
using Uno.UI.RemoteControl.Server.Helpers;
1312
using Uno.UI.RemoteControl.Server.Telemetry;
1413

1514
namespace Uno.UI.RemoteControl.Host.Extensibility;
@@ -18,7 +17,7 @@ public class AddIns
1817
{
1918
private static readonly ILogger _log = typeof(AddIns).Log();
2019

21-
public static IImmutableList<string> Discover(string solutionFile, ITelemetry? telemetry = null)
20+
public static AddInsDiscoveryResult Discover(string solutionFile, ITelemetry? telemetry = null)
2221
{
2322
var startTime = Stopwatch.GetTimestamp();
2423

@@ -31,11 +30,8 @@ public static IImmutableList<string> Discover(string solutionFile, ITelemetry? t
3130

3231
var tmp = Path.GetTempFileName();
3332
var wd = Path.GetDirectoryName(solutionFile);
34-
35-
string DumpTFM(string v) =>
36-
$"build \"{solutionFile}\" -t:UnoDumpTargetFrameworks \"-p:UnoDumpTargetFrameworksTargetFile={tmp}\" \"-p:CustomBeforeMicrosoftCSharpTargets={targetsFile}\" --verbosity {v}";
37-
38-
var command = DumpTFM("quiet");
33+
string DumpTFM(string log) => $"build \"{solutionFile}\" -t:UnoDumpTargetFrameworks \"-p:UnoDumpTargetFrameworksTargetFile={tmp}\" \"-p:CustomBeforeMicrosoftCSharpTargets={targetsFile}\" {log}";
34+
var command = DumpTFM("--verbosity quiet");
3935
var result = ProcessHelper.RunProcess("dotnet", command, wd);
4036
var targetFrameworks = Read(tmp);
4137

@@ -48,35 +44,32 @@ string DumpTFM(string v) =>
4844
+ $"Please fix and restart your IDE (command used: `dotnet {command}`).";
4945
if (result.error is { Length: > 0 })
5046
{
51-
_log.Log(LogLevel.Warning, new Exception(result.error),
52-
msg + " (cf. inner exception for more details.)");
47+
_log.Log(LogLevel.Warning, new Exception(result.error), msg + " (cf. inner exception for more details.)");
5348
}
5449
else
5550
{
56-
result = ProcessHelper.RunProcess("dotnet", DumpTFM("diagnostic"), wd);
51+
var binlog = Path.GetTempFileName();
52+
result = ProcessHelper.RunProcess("dotnet", DumpTFM($"\"-bl:{binlog}\""), wd);
5753

5854
_log.Log(LogLevel.Warning, msg);
5955
_log.Log(LogLevel.Debug, result.output);
6056
}
6157
}
6258

63-
var emptyResult = ImmutableArray<string>.Empty;
64-
TrackDiscoveryCompletion(telemetry, startTime, emptyResult, "NoTargetFrameworks");
65-
return emptyResult;
59+
TrackDiscoveryCompletion(telemetry, startTime, ImmutableArray<string>.Empty, "NoTargetFrameworks");
60+
return AddInsDiscoveryResult.Failed("Failed to determine target frameworks");
6661
}
6762

6863
if (_log.IsEnabled(LogLevel.Debug))
6964
{
70-
_log.Log(LogLevel.Debug,
71-
$"Found target frameworks for solution '{solutionFile}': {string.Join(", ", targetFrameworks)}.");
65+
_log.Log(LogLevel.Debug, $"Found target frameworks for solution '{solutionFile}': {string.Join(", ", targetFrameworks)}.");
7266
}
7367

7468

7569
foreach (var targetFramework in targetFrameworks)
7670
{
7771
tmp = Path.GetTempFileName();
78-
command =
79-
$"build \"{solutionFile}\" -t:UnoDumpRemoteControlAddIns \"-p:UnoDumpRemoteControlAddInsTargetFile={tmp}\" \"-p:CustomBeforeMicrosoftCSharpTargets={targetsFile}\" --verbosity quiet --framework \"{targetFramework}\" -nowarn:MSB4057";
72+
command = $"build \"{solutionFile}\" -t:UnoDumpRemoteControlAddIns \"-p:UnoDumpRemoteControlAddInsTargetFile={tmp}\" \"-p:CustomBeforeMicrosoftCSharpTargets={targetsFile}\" --verbosity quiet --framework \"{targetFramework}\" -nowarn:MSB4057";
8073
result = ProcessHelper.RunProcess("dotnet", command, wd);
8174
if (!string.IsNullOrWhiteSpace(result.error))
8275
{
@@ -102,7 +95,7 @@ string DumpTFM(string v) =>
10295
if (!addIns.IsEmpty)
10396
{
10497
TrackDiscoveryCompletion(telemetry, startTime, addIns, "Success");
105-
return addIns;
98+
return AddInsDiscoveryResult.Success(addIns);
10699
}
107100
}
108101

@@ -111,9 +104,8 @@ string DumpTFM(string v) =>
111104
_log.Log(LogLevel.Information, $"Didn't find any add-ins for solution '{solutionFile}'.");
112105
}
113106

114-
var noAddInsResult = ImmutableArray<string>.Empty;
115-
TrackDiscoveryCompletion(telemetry, startTime, noAddInsResult, "NoAddInsFound");
116-
return noAddInsResult;
107+
TrackDiscoveryCompletion(telemetry, startTime, ImmutableArray<string>.Empty, "NoAddInsFound");
108+
return AddInsDiscoveryResult.Empty();
117109
}
118110
catch (Exception ex)
119111
{
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Collections.Immutable;
2+
3+
namespace Uno.UI.RemoteControl.Host.Extensibility;
4+
5+
public record AddInsDiscoveryResult(string? Error, ImmutableList<string> AddIns)
6+
{
7+
public static AddInsDiscoveryResult Success(ImmutableList<string> assemblies) => new(null, assemblies);
8+
public static AddInsDiscoveryResult Failed(string error) => new(error, ImmutableList<string>.Empty);
9+
public static AddInsDiscoveryResult Empty() => new(null, ImmutableList<string>.Empty);
10+
}

src/Uno.UI.RemoteControl.Host/Extensibility/AddInsExtensions.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Immutable;
23
using System.Linq;
34
using Microsoft.AspNetCore.Hosting;
45
using Microsoft.Extensions.DependencyInjection;
@@ -14,10 +15,16 @@ public static IWebHostBuilder ConfigureAddIns(this IWebHostBuilder builder, stri
1415
{
1516
return builder.ConfigureServices(services =>
1617
{
17-
var discoveredAddIns = AddIns.Discover(solutionFile, telemetry);
18-
var assemblies = AssemblyHelper.Load(discoveredAddIns, telemetry, throwIfLoadFailed: false);
18+
var discovery = AddIns.Discover(solutionFile, telemetry);
19+
var loadResults = AssemblyHelper.Load(discovery.AddIns, telemetry, throwIfLoadFailed: false);
20+
21+
var assemblies = loadResults
22+
.Where(result => result.Assembly is not null)
23+
.Select(result => result.Assembly)
24+
.ToImmutableArray();
1925

2026
services.AddFromAttributes(assemblies);
27+
services.AddSingleton(new AddInsStatus(discovery, loadResults));
2128
});
2229
}
2330
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System.Collections.Immutable;
2+
using Uno.UI.RemoteControl.Helpers;
3+
4+
namespace Uno.UI.RemoteControl.Host.Extensibility;
5+
6+
public record AddInsStatus(AddInsDiscoveryResult Discovery, IImmutableList<AssemblyLoadResult> Assemblies)
7+
{
8+
public static AddInsStatus Empty { get; } = new(AddInsDiscoveryResult.Empty(), ImmutableArray<AssemblyLoadResult>.Empty);
9+
}

src/Uno.UI.RemoteControl.Host/Helpers/AssemblyHelper.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,19 @@
1010

1111
namespace Uno.UI.RemoteControl.Helpers;
1212

13+
public record AssemblyLoadResult(string DllFile, Assembly? Assembly, Exception? Error);
14+
1315
public class AssemblyHelper
1416
{
1517
private static readonly ILogger _log = typeof(AssemblyHelper).Log();
1618

17-
public static IImmutableList<Assembly> Load(IImmutableList<string> dllFiles, ITelemetry? telemetry = null, bool throwIfLoadFailed = false)
19+
public static IImmutableList<AssemblyLoadResult> Load(IImmutableList<string> dllFiles, ITelemetry? telemetry = null, bool throwIfLoadFailed = false)
1820
{
1921
var startTime = Stopwatch.GetTimestamp();
2022

2123
telemetry?.TrackEvent("addin-loading-start", default(Dictionary<string, string>), null);
2224

23-
var assemblies = ImmutableList.CreateBuilder<Assembly>();
25+
var results = ImmutableList.CreateBuilder<AssemblyLoadResult>();
2426
var failedCount = 0;
2527

2628
try
@@ -31,12 +33,13 @@ public static IImmutableList<Assembly> Load(IImmutableList<string> dllFiles, ITe
3133
{
3234
_log.Log(LogLevel.Debug, $"Loading add-in assembly '{dll}'.");
3335

34-
assemblies.Add(Assembly.LoadFrom(dll));
36+
results.Add(new AssemblyLoadResult(dll, Assembly.LoadFrom(dll), null));
3537
}
3638
catch (Exception err)
3739
{
3840
failedCount++;
3941
_log.Log(LogLevel.Error, $"Failed to load assembly '{dll}'.", err);
42+
results.Add(new AssemblyLoadResult(dll, null, err));
4043

4144
if (throwIfLoadFailed)
4245
{
@@ -45,7 +48,7 @@ public static IImmutableList<Assembly> Load(IImmutableList<string> dllFiles, ITe
4548
}
4649
}
4750

48-
var result = assemblies.ToImmutable();
51+
var result = results.ToImmutable();
4952

5053
// Track completion
5154
var completionProperties = new Dictionary<string, string>

src/Uno.UI.RemoteControl.Host/Program.cs

Lines changed: 12 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -27,60 +27,7 @@ static async Task Main(string[] args)
2727

2828
ITelemetry? telemetry = null;
2929

30-
// Set up graceful shutdown handling
31-
using var cancellationTokenSource = new CancellationTokenSource();
32-
var shutdownRequested = false;
33-
34-
Console.CancelKeyPress += (sender, e) =>
35-
{
36-
if (!shutdownRequested)
37-
{
38-
shutdownRequested = true;
39-
e.Cancel = true; // Prevent immediate termination
40-
Console.WriteLine("Graceful shutdown requested...");
41-
cancellationTokenSource.Cancel();
42-
}
43-
};
44-
45-
// Monitor stdin for CTRL-C character (ASCII 3) for graceful shutdown
46-
_ = Task.Run(async () =>
47-
{
48-
try
49-
{
50-
if (!Console.IsInputRedirected)
51-
return;
52-
53-
using var reader = new StreamReader(Console.OpenStandardInput());
54-
55-
var buffer = new char[1];
56-
while (!cancellationTokenSource.Token.IsCancellationRequested)
57-
{
58-
var read = await reader.ReadAsync(buffer, 0, 1);
59-
if (read == 0)
60-
{
61-
break; // EOF
62-
}
63-
64-
if (buffer[0] != '\x03') // CTRL-C (ASCII 3)
65-
{
66-
continue;
67-
}
68-
69-
if (!shutdownRequested)
70-
{
71-
shutdownRequested = true;
72-
Console.WriteLine("Graceful shutdown requested via stdin...");
73-
await cancellationTokenSource.CancelAsync();
74-
}
75-
76-
break;
77-
}
78-
}
79-
catch (Exception ex)
80-
{
81-
Console.WriteLine($"Error monitoring stdin: {ex.Message}");
82-
}
83-
}, cancellationTokenSource.Token);
30+
using var ct = ConsoleHelper.CreateCancellationToken();
8431

8532
try
8633
{
@@ -156,18 +103,18 @@ static async Task Main(string[] args)
156103
.UseUrls($"http://*:{httpPort}/")
157104
.UseContentRoot(Directory.GetCurrentDirectory())
158105
.UseStartup<Startup>()
159-
.ConfigureLogging(logging =>
160-
logging
161-
.ClearProviders()
162-
.AddConsole()
163-
.SetMinimumLevel(LogLevel.Debug))
106+
.ConfigureLogging(logging => logging
107+
.ClearProviders()
108+
.AddConsole()
109+
.SetMinimumLevel(LogLevel.Debug))
164110
.ConfigureAppConfiguration((hostingContext, config) =>
165111
{
166112
config.AddCommandLine(args);
167113
})
168114
.ConfigureServices(services =>
169115
{
170116
services.AddSingleton<IIdeChannel, IdeChannelServer>();
117+
services.AddSingleton<UnoDevEnvironmentService>();
171118

172119
// Add the global service provider to the DI container
173120
services.AddKeyedSingleton<IServiceProvider>("global", globalServiceProvider);
@@ -184,14 +131,17 @@ static async Task Main(string[] args)
184131
else
185132
{
186133
typeof(Program).Log().Log(LogLevel.Warning, "No solution file specified, add-ins will not be loaded which means that you won't be able to use any of the uno-studio features. Usually this indicates that your version of uno's IDE extension is too old.");
134+
builder.ConfigureServices(services => services.AddSingleton(AddInsStatus.Empty));
187135
}
188136

189137
var host = builder.Build();
190138

191139
// Once the app has started, we use the logger from the host
192140
Uno.Extensions.LogExtensionPoint.AmbientLoggerFactory = host.Services.GetRequiredService<ILoggerFactory>();
193141

142+
// Force resolution of the IDEChannel to enable connection (Note: We should use a BackgroundService instead)
194143
host.Services.GetService<IIdeChannel>();
144+
_ = host.Services.GetRequiredService<UnoDevEnvironmentService>().StartAsync(ct.Token); // Background services are not supported by WebHostBuilder
195145

196146
// Display DevServer version banner
197147
DisplayVersionBanner();
@@ -205,19 +155,11 @@ static async Task Main(string[] args)
205155

206156
telemetry?.TrackEvent("startup", startupProperties, null);
207157

208-
_ = ParentProcessObserver.ObserveAsync(
209-
parentPID,
210-
() =>
211-
{
212-
shutdownRequested = true;
213-
cancellationTokenSource.Cancel();
214-
},
215-
telemetry,
216-
cancellationTokenSource.Token);
158+
_ = ParentProcessObserver.ObserveAsync(parentPID, ct.Cancel, telemetry, ct.Token);
217159

218160
try
219161
{
220-
await host.RunAsync(cancellationTokenSource.Token);
162+
await host.RunAsync(ct.Token);
221163
}
222164
finally
223165
{
@@ -227,7 +169,7 @@ static async Task Main(string[] args)
227169
var uptime = TimeSpan.FromTicks(Stopwatch.GetElapsedTime(startTime).Ticks);
228170
var shutdownProperties = new Dictionary<string, string>
229171
{
230-
["ShutdownType"] = shutdownRequested ? "Graceful" : "Crash",
172+
["ShutdownType"] = ct.IsCancellationRequested ? "Graceful" : "Crash",
231173
};
232174
var shutdownMeasurements = new Dictionary<string, double>
233175
{

0 commit comments

Comments
 (0)