Browse Source

Actually call dotnet run with watch (#551)

pull/553/head
Justin Kotalik 6 years ago
committed by GitHub
parent
commit
ca5fd7dbdf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      src/Microsoft.Tye.Core/ProcessSpec.cs
  2. 7
      src/Microsoft.Tye.Core/ProcessUtil.cs
  3. 74
      src/Microsoft.Tye.Hosting/ProcessRunner.cs
  4. 25
      src/Microsoft.Tye.Hosting/Watch/DotNetWatcher.cs
  5. 2
      test/E2ETest/TyeRunTests.cs

3
src/Microsoft.Tye.Core/ProcessSpec.cs

@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
namespace Microsoft.Tye
{
@ -16,8 +17,10 @@ namespace Microsoft.Tye
public IDictionary<string, string> EnvironmentVariables { get; set; } = new Dictionary<string, string>();
public string? Arguments { get; set; }
public Action<string>? OutputData { get; set; }
public Func<Task<int>>? Build { get; set; }
public Action<string>? ErrorData { get; set; }
public Action<int>? OnStart { get; set; }
public Action<int>? OnStop { get; set; }
public string? ShortDisplayName()
=> Path.GetFileNameWithoutExtension(Executable);
}

7
src/Microsoft.Tye.Core/ProcessUtil.cs

@ -26,6 +26,7 @@ namespace Microsoft.Tye
Action<string>? outputDataReceived = null,
Action<string>? errorDataReceived = null,
Action<int>? onStart = null,
Action<int>? onStop = null,
CancellationToken cancellationToken = default)
{
using var process = new Process()
@ -136,12 +137,14 @@ namespace Microsoft.Tye
}
}
return await processLifetimeTask.Task;
var processResult = await processLifetimeTask.Task;
onStop?.Invoke(processResult.ExitCode);
return processResult;
}
public static Task<ProcessResult> RunAsync(ProcessSpec processSpec, CancellationToken cancellationToken = default, bool throwOnError = true)
{
return RunAsync(processSpec.Executable!, processSpec.Arguments!, processSpec.WorkingDirectory, throwOnError: throwOnError, processSpec.EnvironmentVariables, processSpec.OutputData, processSpec.ErrorData, processSpec.OnStart, cancellationToken);
return RunAsync(processSpec.Executable!, processSpec.Arguments!, processSpec.WorkingDirectory, throwOnError: throwOnError, processSpec.EnvironmentVariables, processSpec.OutputData, processSpec.ErrorData, processSpec.OnStart, processSpec.OnStop, cancellationToken);
}
public static void KillProcess(int pid)

74
src/Microsoft.Tye.Hosting/ProcessRunner.cs

@ -237,13 +237,16 @@ namespace Microsoft.Tye.Hosting
{
var replica = serviceName + "_" + Guid.NewGuid().ToString().Substring(0, 10).ToLower();
var status = new ProcessStatus(service, replica);
service.Replicas[replica] = status;
using var stoppingCts = new CancellationTokenSource();
status.StoppingTokenSource = stoppingCts;
await using var _ = processInfo.StoppedTokenSource.Token.Register(() => status.StoppingTokenSource.Cancel());
service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Added, status));
if (!_options.Watch)
{
service.Replicas[replica] = status;
service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Added, status));
}
// This isn't your host name
environment["APP_INSTANCE"] = replica;
@ -297,14 +300,56 @@ namespace Microsoft.Tye.Hosting
status.Pid = pid;
WriteReplicaToStore(pid.ToString());
if (_options.Watch && service.Description.RunInfo is ProjectRunInfo runInfo)
{
// OnStart/OnStop will be called multiple times for watch.
// Watch will constantly be adding and removing from the list, so only add here for watch.
service.Replicas[replica] = status;
service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Added, status));
}
service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Started, status));
},
OnStop = exitCode =>
{
status.ExitCode = exitCode;
if (status.Pid != null)
{
service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Stopped, status));
}
if (!_options.Watch)
{
// Only increase backoff when not watching project as watch will wait for file changes before rebuild.
backOff *= 2;
}
service.Restarts++;
service.Replicas.TryRemove(replica, out var _);
service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Removed, status));
if (status.ExitCode != null)
{
_logger.LogInformation("{ServiceName} process exited with exit code {ExitCode}", replica, status.ExitCode);
}
},
Build = async () =>
{
var buildResult = await ProcessUtil.RunAsync("dotnet", $"build \"{service.Status.ProjectFilePath}\" /nologo", throwOnError: false, workingDirectory: application.ContextDirectory);
if (buildResult.ExitCode != 0)
{
_logger.LogInformation("Building projects failed with exit code {ExitCode}: \r\n" + buildResult.StandardOutput, buildResult.ExitCode);
}
return buildResult.ExitCode;
}
};
if (_options.Watch && service.Description.RunInfo is ProjectRunInfo runInfo)
{
var projectFile = runInfo.ProjectFile.FullName;
var fileSetFactory = new MsBuildFileSetFactory(_logger,
projectFile,
waitOnError: true,
@ -316,16 +361,8 @@ namespace Microsoft.Tye.Hosting
}
else
{
var result = await ProcessUtil.RunAsync(processInfo, status.StoppingTokenSource.Token, throwOnError: false);
status.ExitCode = result.ExitCode;
if (status.Pid != null)
{
service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Stopped, status));
}
await ProcessUtil.RunAsync(processInfo, status.StoppingTokenSource.Token, throwOnError: false);
}
}
catch (Exception ex)
{
@ -340,19 +377,6 @@ namespace Microsoft.Tye.Hosting
// Swallow cancellation exceptions and continue
}
}
backOff *= 2;
service.Restarts++;
if (status.ExitCode != null)
{
_logger.LogInformation("{ServiceName} process exited with exit code {ExitCode}", replica, status.ExitCode);
}
// Remove the replica from the set
service.Replicas.TryRemove(replica, out var _);
service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Removed, status));
}
}

25
src/Microsoft.Tye.Hosting/Watch/DotNetWatcher.cs

@ -31,6 +31,11 @@ namespace Microsoft.DotNet.Watcher
while (true)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = iteration.ToString(CultureInfo.InvariantCulture);
iteration++;
@ -42,11 +47,6 @@ namespace Microsoft.DotNet.Watcher
return;
}
if (cancellationToken.IsCancellationRequested)
{
return;
}
using (var currentRunCancellationSource = new CancellationTokenSource())
using (var combinedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken,
@ -95,6 +95,21 @@ namespace Microsoft.DotNet.Watcher
{
_logger.LogInformation($"watch: File changed: {fileSetTask.Result}");
}
if (processSpec.Build != null)
{
while (true)
{
var exitCode = await processSpec.Build();
if (exitCode == 0)
{
break;
// Build failed, keep retrying builds until successful build.
}
await fileSetWatcher.GetChangedFileAsync(cancellationToken, () => _logger.LogWarning("Waiting for a file to change before restarting dotnet..."));
}
}
}
}
}

2
test/E2ETest/TyeRunTests.cs

@ -291,7 +291,7 @@ namespace E2ETest
return;
}
await Task.Delay(500);
await Task.Delay(5000);
}
throw new Exception("Failed to relaunch project with dotnet watch");

Loading…
Cancel
Save