Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WithExitCondition feature #242

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 36 additions & 4 deletions CliWrap/Command.Execution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,11 +219,17 @@ private async Task<CommandResult> ExecuteAsync(
.Register(process.Interrupt)
.ToAsyncDisposable();

// Create token to cancel piping when process is finished and we don't need to finish piping
using var outputProcessingCts = new CancellationTokenSource();
using var pipeStdOutErrCts = CancellationTokenSource.CreateLinkedTokenSource(
forcefulCancellationToken,
outputProcessingCts.Token
);
// Start piping streams in the background
var pipingTask = Task.WhenAll(
PipeStandardInputAsync(process, stdInCts.Token),
PipeStandardOutputAsync(process, forcefulCancellationToken),
PipeStandardErrorAsync(process, forcefulCancellationToken)
PipeStandardOutputAsync(process, pipeStdOutErrCts.Token),
PipeStandardErrorAsync(process, pipeStdOutErrCts.Token)
);

try
Expand All @@ -239,8 +245,34 @@ private async Task<CommandResult> ExecuteAsync(
// If the pipe is still trying to transfer data, this will cause it to abort.
await stdInCts.CancelAsync();

// Wait until piping is done and propagate exceptions
await pipingTask.ConfigureAwait(false);
if (CommandExitCondition == CommandExitCondition.PipesClosed)
{
try
{
// Wait until piping is done and propagate exceptions
await pipingTask.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// throw original token if it was the source of cancel
forcefulCancellationToken.ThrowIfCancellationRequested();
throw;
}
}
else if (CommandExitCondition == CommandExitCondition.ProcessExited)
{
try
{
// Cancel piping if we don't need to wait for it
await outputProcessingCts.CancelAsync();
await pipingTask.ConfigureAwait(false);
}
catch (OperationCanceledException) { }
}
else
{
throw new NotImplementedException($"{CommandExitCondition} is not implemented.");
}
}
// Swallow exceptions caused by internal and user-provided cancellations,
// because we have a separate mechanism for handling them below.
Expand Down
54 changes: 43 additions & 11 deletions CliWrap/Command.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public partial class Command(
CommandResultValidation validation,
PipeSource standardInputPipe,
PipeTarget standardOutputPipe,
PipeTarget standardErrorPipe
PipeTarget standardErrorPipe,
CommandExitCondition commandExitCondition
) : ICommandConfiguration
{
/// <summary>
Expand All @@ -35,7 +36,8 @@ public Command(string targetFilePath)
CommandResultValidation.ZeroExitCode,
PipeSource.Null,
PipeTarget.Null,
PipeTarget.Null
PipeTarget.Null,
CommandExitCondition.PipesClosed
) { }

/// <inheritdoc />
Expand All @@ -50,6 +52,9 @@ public Command(string targetFilePath)
/// <inheritdoc />
public Credentials Credentials { get; } = credentials;

/// <inheritdoc />
public CommandExitCondition CommandExitCondition { get; } = commandExitCondition;
dmilosz marked this conversation as resolved.
Show resolved Hide resolved

/// <inheritdoc />
public IReadOnlyDictionary<string, string?> EnvironmentVariables { get; } =
environmentVariables;
Expand Down Expand Up @@ -80,7 +85,8 @@ public Command WithTargetFile(string targetFilePath) =>
Validation,
StandardInputPipe,
StandardOutputPipe,
StandardErrorPipe
StandardErrorPipe,
CommandExitCondition
);

/// <summary>
Expand All @@ -101,7 +107,8 @@ public Command WithArguments(string arguments) =>
Validation,
StandardInputPipe,
StandardOutputPipe,
StandardErrorPipe
StandardErrorPipe,
CommandExitCondition
);

/// <summary>
Expand Down Expand Up @@ -147,7 +154,8 @@ public Command WithWorkingDirectory(string workingDirPath) =>
Validation,
StandardInputPipe,
StandardOutputPipe,
StandardErrorPipe
StandardErrorPipe,
CommandExitCondition
);

/// <summary>
Expand All @@ -164,7 +172,8 @@ public Command WithCredentials(Credentials credentials) =>
Validation,
StandardInputPipe,
StandardOutputPipe,
StandardErrorPipe
StandardErrorPipe,
CommandExitCondition
);

/// <summary>
Expand Down Expand Up @@ -196,7 +205,8 @@ public Command WithEnvironmentVariables(
Validation,
StandardInputPipe,
StandardOutputPipe,
StandardErrorPipe
StandardErrorPipe,
CommandExitCondition
);

/// <summary>
Expand Down Expand Up @@ -226,7 +236,26 @@ public Command WithValidation(CommandResultValidation validation) =>
validation,
StandardInputPipe,
StandardOutputPipe,
StandardErrorPipe
StandardErrorPipe,
CommandExitCondition
);

/// <summary>
/// Creates a copy of this command, setting the exit condition to the specified value.
/// </summary>
[Pure]
public Command WithExitCondition(CommandExitCondition commandExitCondition) =>
new(
TargetFilePath,
Arguments,
WorkingDirPath,
Credentials,
EnvironmentVariables,
Validation,
StandardInputPipe,
StandardOutputPipe,
StandardErrorPipe,
commandExitCondition
);

/// <summary>
Expand All @@ -243,7 +272,8 @@ public Command WithStandardInputPipe(PipeSource source) =>
Validation,
source,
StandardOutputPipe,
StandardErrorPipe
StandardErrorPipe,
CommandExitCondition
);

/// <summary>
Expand All @@ -260,7 +290,8 @@ public Command WithStandardOutputPipe(PipeTarget target) =>
Validation,
StandardInputPipe,
target,
StandardErrorPipe
StandardErrorPipe,
CommandExitCondition
);

/// <summary>
Expand All @@ -277,7 +308,8 @@ public Command WithStandardErrorPipe(PipeTarget target) =>
Validation,
StandardInputPipe,
StandardOutputPipe,
target
target,
CommandExitCondition
);

/// <inheritdoc />
Expand Down
21 changes: 21 additions & 0 deletions CliWrap/CommandExitCondition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;

namespace CliWrap;
dmilosz marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Strategy used for veryfing the end of command exectuion.
dmilosz marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
[Flags]
dmilosz marked this conversation as resolved.
Show resolved Hide resolved
public enum CommandExitCondition
{
/// <summary>
/// Command is finished when process is finished and all pipes are closed.
dmilosz marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
PipesClosed = 0,

/// <summary>
/// Command is finished when the main process exits,
/// even if they are child processes still running, which are reusing the same output/error streams.
dmilosz marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
ProcessExited = 1
}