@@ -37,7 +37,6 @@ public partial class EntryPoint : IDisposable
37
37
{
38
38
private const string UnoPlatformOutputPane = "Uno Platform" ;
39
39
private const string RemoteControlServerPortProperty = "UnoRemoteControlPort" ;
40
- private const string UnoRemoteControlConfigCookieProperty = "UnoRemoteControlConfigCookie" ;
41
40
private const string UnoVSExtensionLoadedProperty = "_UnoVSExtensionLoaded" ;
42
41
43
42
private readonly CancellationTokenSource _ct = new ( ) ;
@@ -51,12 +50,10 @@ public partial class EntryPoint : IDisposable
51
50
private Action < string > ? _warningAction ;
52
51
private Action < string > ? _errorAction ;
53
52
private int _msBuildLogLevel ;
54
- private System . Diagnostics . Process ? _process ;
55
- private SemaphoreSlim _processGate = new ( 1 ) ;
53
+ private ( System . Diagnostics . Process process , int port ) ? _devServer ;
54
+ private SemaphoreSlim _devServerGate = new ( 1 ) ;
56
55
private IServiceProvider ? _visualStudioServiceProvider ;
57
56
58
- private int _remoteControlServerPort ;
59
- private string ? _remoteControlConfigCookie ;
60
57
private bool _closing ;
61
58
private bool _isDisposed ;
62
59
private IdeChannelClient ? _ideChannelClient ;
@@ -139,16 +136,6 @@ private Task<Dictionary<string, string>> OnProvideGlobalPropertiesAsync()
139
136
[ UnoVSExtensionLoadedProperty ] = "true"
140
137
} ;
141
138
142
- if ( _remoteControlServerPort is not 0 )
143
- {
144
- properties . Add ( RemoteControlServerPortProperty , _remoteControlServerPort . ToString ( CultureInfo . InvariantCulture ) ) ;
145
- }
146
-
147
- if ( _remoteControlConfigCookie is not null )
148
- {
149
- properties . Add ( UnoRemoteControlConfigCookieProperty , _remoteControlConfigCookie ) ;
150
- }
151
-
152
139
return Task . FromResult ( properties ) ;
153
140
}
154
141
@@ -173,8 +160,8 @@ private void SetupOutputWindow()
173
160
if ( owPane == null )
174
161
{
175
162
owPane = ow
176
- . OutputWindowPanes
177
- . Add ( UnoPlatformOutputPane ) ;
163
+ . OutputWindowPanes
164
+ . Add ( UnoPlatformOutputPane ) ;
178
165
}
179
166
180
167
_debugAction = s =>
@@ -252,24 +239,24 @@ private void SolutionEvents_BeforeClosing()
252
239
_dte . Events . SolutionEvents . BeforeClosing -= _closeHandler ;
253
240
254
241
_closing = true ;
255
- if ( _process is not null )
242
+ if ( _devServer is { process : var devServer } )
256
243
{
257
244
try
258
245
{
259
- _debugAction ? . Invoke ( $ "Terminating Remote Control server (pid: { _process . Id } )") ;
260
- _process . Kill ( ) ;
261
- _debugAction ? . Invoke ( $ "Terminated Remote Control server (pid: { _process . Id } )") ;
246
+ _debugAction ? . Invoke ( $ "Terminating Remote Control server (pid: { devServer . Id } )") ;
247
+ devServer . Kill ( ) ;
248
+ _debugAction ? . Invoke ( $ "Terminated Remote Control server (pid: { devServer . Id } )") ;
262
249
263
250
_ideChannelClient ? . Dispose ( ) ;
264
251
_ideChannelClient = null ;
265
252
}
266
253
catch ( Exception e )
267
254
{
268
- _debugAction ? . Invoke ( $ "Failed to terminate Remote Control server (pid: { _process . Id } ): { e } ") ;
255
+ _debugAction ? . Invoke ( $ "Failed to terminate Remote Control server (pid: { devServer . Id } ): { e } ") ;
269
256
}
270
257
finally
271
258
{
272
- _process = null ;
259
+ _devServer = null ;
273
260
274
261
// Invoke Dispose to make sure other event handlers are detached
275
262
Dispose ( ) ;
@@ -301,27 +288,45 @@ private async Task EnsureServerAsync()
301
288
{
302
289
_debugAction ? . Invoke ( $ "Starting server (tid:{ Environment . CurrentManagedThreadId } )") ;
303
290
304
- if ( _process is { HasExited : false } )
291
+ var persistedPortsStr = await _dte . GetProjectUserSettingsAsync ( _asyncPackage , RemoteControlServerPortProperty , ProjectAttribute . Application ) ;
292
+ var persistedPorts = persistedPortsStr
293
+ . Distinct ( StringComparer . OrdinalIgnoreCase )
294
+ . Select ( str => int . TryParse ( str , out var p ) ? p : - 1 )
295
+ . ToArray ( ) ;
296
+ var persistedPort = persistedPorts . FirstOrDefault ( p => p > 0 ) ;
297
+
298
+ var port = _devServer ? . port ?? persistedPort ;
299
+ var portMissConfigured = persistedPorts is { Length : 0 or > 1 } || port != persistedPort ;
300
+
301
+ if ( _devServer is { process . HasExited : false } )
305
302
{
306
- _debugAction ? . Invoke ( "Server already running" ) ;
303
+ if ( portMissConfigured )
304
+ {
305
+ _debugAction ? . Invoke ( $ "Server already running on port { _devServer ? . port } , but port is not configured properly on all projects. Updating it ...") ;
306
+
307
+ // The dev-server is already running, but at least one project is not configured properly
308
+ // (This can happen when a project is being added to the solution while opened - Reminder: This EnsureServerAsync is invoked each time a project is built)
309
+ // We make sure to set the current port for **all** projects.
310
+ await _dte . SetProjectUserSettingsAsync ( _asyncPackage , RemoteControlServerPortProperty , port . ToString ( CultureInfo . InvariantCulture ) , ProjectAttribute . Application ) ;
311
+ }
312
+ else
313
+ {
314
+ _debugAction ? . Invoke ( $ "Server already running on port { _devServer ? . port } ") ;
315
+ }
316
+
307
317
return ;
308
318
}
309
319
310
- await _processGate . WaitAsync ( ) ;
320
+ await _devServerGate . WaitAsync ( ) ;
311
321
try
312
322
{
313
-
314
- if ( EnsureTcpPort ( ref _remoteControlServerPort ) )
323
+ if ( EnsureTcpPort ( ref port ) || portMissConfigured )
315
324
{
316
- // Update the cookie file, so a rebuild will be triggered
317
- _remoteControlConfigCookie ??= Path . GetTempFileName ( ) ;
318
- File . WriteAllText ( _remoteControlConfigCookie , _remoteControlServerPort . ToString ( CultureInfo . InvariantCulture ) ) ;
319
-
320
- // Push the new port to the project using global properties
321
- _ = _globalPropertiesChanged ( ) ;
325
+ // The port has changed, or all application projects does not have the same port number (or is not configured), we update port in *all* user files
326
+ await _dte . SetProjectUserSettingsAsync ( _asyncPackage , RemoteControlServerPortProperty , port . ToString ( CultureInfo . InvariantCulture ) , ProjectAttribute . Application ) ;
322
327
}
323
328
324
- _debugAction ? . Invoke ( $ "Using available port { _remoteControlServerPort } ") ;
329
+ _debugAction ? . Invoke ( $ "Using available port { port } ") ;
325
330
326
331
var version = GetDotnetMajorVersion ( ) ;
327
332
if ( version < 7 )
@@ -332,7 +337,7 @@ private async Task EnsureServerAsync()
332
337
var pipeGuid = Guid . NewGuid ( ) ;
333
338
334
339
var hostBinPath = Path . Combine ( _toolsPath , "host" , $ "net{ version } .0", "Uno.UI.RemoteControl.Host.dll" ) ;
335
- var arguments = $ "\" { hostBinPath } \" --httpPort { _remoteControlServerPort } --ppid { System . Diagnostics . Process . GetCurrentProcess ( ) . Id } --ideChannel \" { pipeGuid } \" --solution \" { _dte . Solution . FullName } \" ";
340
+ var arguments = $ "\" { hostBinPath } \" --httpPort { port } --ppid { System . Diagnostics . Process . GetCurrentProcess ( ) . Id } --ideChannel \" { pipeGuid } \" --solution \" { _dte . Solution . FullName } \" ";
336
341
var pi = new ProcessStartInfo ( "dotnet" , arguments )
337
342
{
338
343
UseShellExecute = false ,
@@ -345,55 +350,29 @@ private async Task EnsureServerAsync()
345
350
RedirectStandardError = true
346
351
} ;
347
352
348
- _process = new System . Diagnostics . Process { EnableRaisingEvents = true } ;
353
+ var devServer = new System . Diagnostics . Process { EnableRaisingEvents = true } ;
354
+ _devServer = ( devServer , port ) ;
349
355
350
356
// hookup the event handlers to capture the data that is received
351
- _process . OutputDataReceived += ( sender , args ) => _debugAction ? . Invoke ( args . Data ) ;
352
- _process . ErrorDataReceived += ( sender , args ) => _errorAction ? . Invoke ( args . Data ) ;
357
+ devServer . OutputDataReceived += ( sender , args ) => _debugAction ? . Invoke ( args . Data ) ;
358
+ devServer . ErrorDataReceived += ( sender , args ) => _errorAction ? . Invoke ( args . Data ) ;
353
359
354
- _process . StartInfo = pi ;
355
- _process . Exited += ( sender , args ) => _ = RestartAsync ( ) ;
360
+ devServer . StartInfo = pi ;
361
+ devServer . Exited += ( sender , args ) => _ = RestartAsync ( ) ;
356
362
357
- if ( _process . Start ( ) )
363
+ if ( devServer . Start ( ) )
358
364
{
359
365
// start our event pumps
360
- _process . BeginOutputReadLine ( ) ;
361
- _process . BeginErrorReadLine ( ) ;
366
+ devServer . BeginOutputReadLine ( ) ;
367
+ devServer . BeginErrorReadLine ( ) ;
362
368
363
369
_ideChannelClient = new IdeChannelClient ( pipeGuid , new Logger ( this ) ) ;
364
370
_ideChannelClient . OnMessageReceived += OnMessageReceivedAsync ;
365
371
_ideChannelClient . ConnectToHost ( ) ;
366
-
367
- // Set the port to the projects
368
- // This LEGACY as port should be set through the global properties (cf. OnProvideGlobalPropertiesAsync)
369
- var portString = _remoteControlServerPort . ToString ( CultureInfo . InvariantCulture ) ;
370
- await ThreadHelper . JoinableTaskFactory . SwitchToMainThreadAsync ( ) ;
371
- var projects = ( await _dte . GetProjectsAsync ( ) ) . ToArray ( ) ; // EnumerateProjects must be called on the UI thread.
372
- await TaskScheduler . Default ;
373
- foreach ( var project in projects )
374
- {
375
- var filename = string . Empty ;
376
- try
377
- {
378
- filename = project . FileName ;
379
- }
380
- catch ( Exception ex )
381
- {
382
- _debugAction ? . Invoke ( $ "Exception on retrieving { project . UniqueName } details. Err: { ex } .") ;
383
- _warningAction ? . Invoke ( $ "Cannot read { project . UniqueName } project details (It may be unloaded).") ;
384
- }
385
- if ( string . IsNullOrWhiteSpace ( filename ) == false
386
- && GetMsbuildProject ( filename ) is Microsoft . Build . Evaluation . Project msbProject
387
- && IsApplication ( msbProject ) )
388
- {
389
- SetGlobalProperty ( filename , RemoteControlServerPortProperty , portString ) ;
390
- }
391
- }
392
372
}
393
373
else
394
374
{
395
- _process = null ;
396
- _remoteControlServerPort = 0 ;
375
+ _devServer = null ;
397
376
}
398
377
}
399
378
catch ( Exception e )
@@ -402,33 +381,23 @@ private async Task EnsureServerAsync()
402
381
}
403
382
finally
404
383
{
405
- _processGate . Release ( ) ;
384
+ _devServerGate . Release ( ) ;
406
385
}
407
386
408
387
async Task RestartAsync ( )
409
388
{
410
389
if ( _closing || _ct . IsCancellationRequested )
411
390
{
412
- if ( _remoteControlConfigCookie is not null )
413
- {
414
- File . WriteAllText ( _remoteControlConfigCookie , "--closed--" ) ; // Make sure VS will re-build on next start
415
- }
416
-
417
- _debugAction ? . Invoke ( $ "Remote Control server exited ({ _process ? . ExitCode } ) and won't be restarted as solution is closing.") ;
391
+ _debugAction ? . Invoke ( $ "Remote Control server exited ({ _devServer ? . process . ExitCode } ) and won't be restarted as solution is closing.") ;
418
392
return ;
419
393
}
420
394
421
- _debugAction ? . Invoke ( $ "Remote Control server exited ({ _process ? . ExitCode } ). It will restart in 5sec.") ;
395
+ _debugAction ? . Invoke ( $ "Remote Control server exited ({ _devServer ? . process . ExitCode } ). It will restart in 5sec.") ;
422
396
423
397
await Task . Delay ( 5000 , _ct . Token ) ;
424
398
425
399
if ( _closing || _ct . IsCancellationRequested )
426
400
{
427
- if ( _remoteControlConfigCookie is not null )
428
- {
429
- File . WriteAllText ( _remoteControlConfigCookie , "--closed--" ) ; // Make sure VS will re-build on next start
430
- }
431
-
432
401
_debugAction ? . Invoke ( $ "Remote Control server will not be restarted as solution is closing.") ;
433
402
return ;
434
403
}
@@ -653,51 +622,6 @@ private bool EnsureTcpPort(ref int port)
653
622
return true ; // HasChanged
654
623
}
655
624
656
- public void SetGlobalProperty ( string projectFullName , string propertyName , string propertyValue )
657
- {
658
- var msbuildProject = GetMsbuildProject ( projectFullName ) ;
659
- if ( msbuildProject == null )
660
- {
661
- _debugAction ? . Invoke ( $ "Failed to find project { projectFullName } , cannot provide listen port to the app.") ;
662
- }
663
- else
664
- {
665
- SetGlobalProperty ( msbuildProject , propertyName , propertyValue ) ;
666
- }
667
- }
668
-
669
- private static Microsoft . Build . Evaluation . Project ? GetMsbuildProject ( string projectFullName )
670
- => ProjectCollection . GlobalProjectCollection . GetLoadedProjects ( projectFullName ) . FirstOrDefault ( ) ;
671
-
672
- public void SetGlobalProperties ( string projectFullName , IDictionary < string , string > properties )
673
- {
674
- var msbuildProject = ProjectCollection . GlobalProjectCollection . GetLoadedProjects ( projectFullName ) . FirstOrDefault ( ) ;
675
- if ( msbuildProject == null )
676
- {
677
- _debugAction ? . Invoke ( $ "Failed to find project { projectFullName } , cannot provide listen port to the app.") ;
678
- }
679
- else
680
- {
681
- foreach ( var property in properties )
682
- {
683
- SetGlobalProperty ( msbuildProject , property . Key , property . Value ) ;
684
- }
685
- }
686
- }
687
-
688
- private void SetGlobalProperty ( Microsoft . Build . Evaluation . Project msbuildProject , string propertyName , string propertyValue )
689
- {
690
- msbuildProject . SetGlobalProperty ( propertyName , propertyValue ) ;
691
-
692
- }
693
-
694
- private bool IsApplication ( Microsoft . Build . Evaluation . Project project )
695
- {
696
- var outputType = project . GetPropertyValue ( "OutputType" ) ;
697
- return outputType is not null &&
698
- ( outputType . Equals ( "Exe" , StringComparison . OrdinalIgnoreCase ) || outputType . Equals ( "WinExe" , StringComparison . OrdinalIgnoreCase ) ) ;
699
- }
700
-
701
625
public void Dispose ( )
702
626
{
703
627
if ( _isDisposed )
0 commit comments