param(
[string]$ConfigPath = ".\config.sexp",
[string]$ServerHost = "127.0.0.1",
[int]$Port = 7777,
[string[]]$Command,
[string]$CommandsPath,
[switch]$UseExistingServer,
[switch]$UseLocalServer,
[switch]$SkipBuild,
[switch]$NoInteractive
)
$ErrorActionPreference = "Stop"
function Invoke-Build {
Write-Host "Building Sakura..."
dune build
if (-not $?) {
throw "Build failed."
}
}
function Start-SakuraServer {
param(
[string]$Config,
[string]$WorkingDirectory,
[switch]$UseOpam
)
Write-Host "Starting Sakura server with config $Config ..."
$filePath = "dune"
$serverArgs = @("exec", "sakura-server", "--", $Config)
if ($UseOpam) {
$filePath = "opam"
$serverArgs = @("exec", "--", "sakura-server", $Config)
}
$proc = Start-Process -FilePath $filePath `
-ArgumentList $serverArgs `
-WorkingDirectory $WorkingDirectory `
-PassThru
return $proc
}
function Wait-ForTcp {
param(
[string]$TargetHost,
[int]$TargetPort,
[int]$TimeoutSeconds = 20
)
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
while ((Get-Date) -lt $deadline) {
$client = $null
try {
$client = [System.Net.Sockets.TcpClient]::new()
$async = $client.BeginConnect($TargetHost, $TargetPort, $null, $null)
if ($async.AsyncWaitHandle.WaitOne(250)) {
$client.EndConnect($async)
$client.Close()
return
}
$client.Close()
} catch {
if ($null -ne $client) {
$client.Close()
}
Start-Sleep -Milliseconds 250
}
}
throw "Timed out waiting for TCP server at $TargetHost`:$TargetPort"
}
function Connect-Tcp {
param(
[string]$TargetHost,
[int]$TargetPort
)
$client = [System.Net.Sockets.TcpClient]::new($TargetHost, $TargetPort)
$stream = $client.GetStream()
$writer = [System.IO.StreamWriter]::new($stream)
$writer.AutoFlush = $true
$reader = [System.IO.StreamReader]::new($stream)
return @{
Client = $client
Writer = $writer
Reader = $reader
}
}
function Send-Command {
param(
[System.IO.StreamWriter]$Writer,
[System.IO.StreamReader]$Reader,
[string]$Line
)
$Writer.WriteLine($Line)
$response = $Reader.ReadLine()
if ($null -eq $response) {
throw "Connection closed by server."
}
Write-Host "< $response"
}
$repoRoot = $PSScriptRoot
$serverProcess = $null
try {
if (-not $SkipBuild) {
Invoke-Build
}
if (-not $UseExistingServer) {
if ($UseLocalServer) {
Write-Host "Server mode: local dune exec sakura-server"
} else {
Write-Host "Server mode: opam-installed sakura-server"
}
$serverProcess = Start-SakuraServer -Config $ConfigPath -WorkingDirectory $repoRoot -UseOpam:(-not $UseLocalServer)
Wait-ForTcp -TargetHost $ServerHost -TargetPort $Port
Write-Host "Server is ready on $ServerHost`:$Port"
}
$conn = Connect-Tcp -TargetHost $ServerHost -TargetPort $Port
$writer = $conn.Writer
$reader = $conn.Reader
$commandsToSend = @()
if ($null -ne $Command -and $Command.Count -gt 0) {
$commandsToSend += $Command
}
if (-not [string]::IsNullOrWhiteSpace($CommandsPath)) {
$fileCommands = Get-Content -Path $CommandsPath |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
$commandsToSend += $fileCommands
}
if ($commandsToSend.Count -gt 0) {
foreach ($cmd in $commandsToSend) {
Write-Host "> $cmd"
Send-Command -Writer $writer -Reader $reader -Line $cmd
}
}
if (-not $NoInteractive) {
Write-Host "Interactive mode. Type :quit to exit."
while ($true) {
$line = Read-Host ">"
if ($line -eq ":quit") {
break
}
if ([string]::IsNullOrWhiteSpace($line)) {
continue
}
Send-Command -Writer $writer -Reader $reader -Line $line
}
}
$writer.Dispose()
$reader.Dispose()
$conn.Client.Close()
}
finally {
if ($null -ne $serverProcess -and -not $serverProcess.HasExited) {
Write-Host "Stopping Sakura server (PID $($serverProcess.Id))"
Stop-Process -Id $serverProcess.Id -Force
}
}