aboutsummaryrefslogblamecommitdiffstats
path: root/run_tcp.ps1
blob: 175fc26ccff9510e0252f10b7c72bd7d7c5b5f3f (plain) (tree)














































































































































































                                                                                                                       
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
  }
}