Initial commit: Nix WSL Wrapper project with DDD, Clean Code, and tests

This commit is contained in:
Marek Novák
2026-07-01 02:04:43 +02:00
commit 161eb7d387
22 changed files with 932 additions and 0 deletions
@@ -0,0 +1,61 @@
using System.Diagnostics;
namespace NixWslWrapper.Infrastructure.Executors
{
public class ProcessBuilder
{
private readonly ProcessStartInfo _startInfo = new();
public ProcessBuilder SetFileName(string fileName)
{
_startInfo.FileName = fileName;
return this;
}
public ProcessBuilder SetArguments(string arguments)
{
_startInfo.Arguments = arguments;
return this;
}
public ProcessBuilder RedirectInput(bool redirect = true)
{
_startInfo.RedirectStandardInput = redirect;
return this;
}
public ProcessBuilder RedirectOutput(bool redirect = true)
{
_startInfo.RedirectStandardOutput = redirect;
return this;
}
public ProcessBuilder RedirectError(bool redirect = true)
{
_startInfo.RedirectStandardError = redirect;
return this;
}
public ProcessBuilder UseShellExecute(bool useShell = false)
{
_startInfo.UseShellExecute = useShell;
return this;
}
public ProcessBuilder CreateNoWindow(bool noWindow = true)
{
_startInfo.CreateNoWindow = noWindow;
return this;
}
public ProcessStartInfo Build()
{
return _startInfo;
}
public Process CreateProcess()
{
return new Process { StartInfo = _startInfo };
}
}
}
@@ -0,0 +1,85 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using NixWslWrapper.Core.Domain;
using NixWslWrapper.Core.Interfaces;
namespace NixWslWrapper.Infrastructure.Executors
{
public class WslCommandExecutor : ICommandExecutor
{
private readonly IStreamCopier _streamCopier;
private readonly Stream _hostIn;
private readonly Stream _hostOut;
private readonly Stream _hostErr;
public WslCommandExecutor(
IStreamCopier streamCopier,
Stream? hostIn = null,
Stream? hostOut = null,
Stream? hostErr = null)
{
_streamCopier = streamCopier ?? throw new ArgumentNullException(nameof(streamCopier));
_hostIn = hostIn ?? Console.OpenStandardInput();
_hostOut = hostOut ?? Console.OpenStandardOutput();
_hostErr = hostErr ?? Console.OpenStandardError();
}
public ExecutionResult Execute(NixArguments arguments, WslTarget target)
{
if (arguments == null) throw new ArgumentNullException(nameof(arguments));
if (target == null) throw new ArgumentNullException(nameof(target));
string wslCmd = $". {target.NixProfilePath} && nix {arguments.ToEscapedString()}";
string wslArgs = $"-d {target.Distribution} -u {target.User} -- sh -c \"{wslCmd.Replace("\"", "\\\"")}\"";
var processBuilder = new ProcessBuilder()
.SetFileName("wsl.exe")
.SetArguments(wslArgs)
.RedirectInput(true)
.RedirectOutput(true)
.RedirectError(true)
.UseShellExecute(false)
.CreateNoWindow(true);
using (Process process = processBuilder.CreateProcess())
{
try
{
process.Start();
// Standard streams copy tasks
Thread tOut = new(() => _streamCopier.Copy(process.StandardOutput.BaseStream, _hostOut))
{
IsBackground = true
};
Thread tErr = new(() => _streamCopier.Copy(process.StandardError.BaseStream, _hostErr))
{
IsBackground = true
};
Thread tIn = new(() => _streamCopier.Copy(_hostIn, process.StandardInput.BaseStream))
{
IsBackground = true
};
tOut.Start();
tErr.Start();
tIn.Start();
process.WaitForExit();
// Wait up to 1 second for stream copier threads to finalize flushing buffer content
tOut.Join(1000);
tErr.Join(1000);
return new ExecutionResult(process.ExitCode);
}
catch (Exception ex)
{
return ExecutionResult.Failure(1, $"Error executing Nix wrapper: {ex.Message}");
}
}
}
}
}
@@ -0,0 +1,44 @@
using System;
using System.IO;
using NixWslWrapper.Core.Interfaces;
namespace NixWslWrapper.Infrastructure.IO
{
public class ThreadedStreamCopier : IStreamCopier
{
private readonly int _bufferSize;
public ThreadedStreamCopier(int bufferSize = 8192)
{
if (bufferSize <= 0)
throw new ArgumentOutOfRangeException(nameof(bufferSize), "Buffer size must be positive.");
_bufferSize = bufferSize;
}
public void Copy(Stream input, Stream output)
{
byte[] buffer = new byte[_bufferSize];
int read;
try
{
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, read);
output.Flush();
}
}
catch (IOException)
{
// Clean exit when stream is closed or process terminates
}
catch (ObjectDisposedException)
{
// Clean exit when streams are disposed
}
catch (Exception)
{
// General catch to ensure thread doesn't crash the host process
}
}
}
}
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\NixWslWrapper.Core\NixWslWrapper.Core.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>