/// <ref><url>https://wiki.archlinux.org/title/Arduino</url></ref>
module Frduino.With_prgmr
open System.Diagnostics
open Stdlib.Syntax
open UnityEngine
type t = session
and session =
{ get_lock : string
; get_last_status : string
}
static member v () =
{ get_lock = "/tmp/tmp.test1"
; get_last_status = "/tmp/tmp.test2"
}
/// combined with <code>option</code> to add state Indeterminate
type status = status' option
and status' =
| Ok
| Failed
override this.ToString () =
match this with
| Ok -> "ok"
| Failed -> "failed"
static member of_string = function
| "ok" -> Ok
| "failed" -> Failed
| _ -> failwith "sdfssdf"
type File = System.IO.File
type String = System.String
exception Not_found = System.IO.FileNotFoundException
let memo code t f = task {
// use! _ = mut.EnterAsync (new System.TimeSpan (0,1,0))
let! last_status = task {
try let! x = File.ReadAllTextAsync t.get_last_status in return Some @@ status'.of_string x
with :? Not_found -> return None }
let! code_prev = task {
try return! File.ReadAllTextAsync t.get_lock
with :? Not_found -> return "" }
// Debug.Log $"[memo log] code_prev: '{code_prev}'"
Debug.Log $"[memo log] status: '{last_status}'"
if String.Equals (code, code_prev) then
Debug.Log "[memo log] cache hit"
return ()
else
Debug.Log "[memo log] cache miss"
Debug.Log "[memo log] done stopping"
do! File.WriteAllTextAsync (t.get_lock, code)
Debug.Log "[memo log] done stopping"
let! a = task {
try let! () = Async.StartAsTask f in return Ok
with e -> let () = Debug.Log $"[memo error] failed to run continuation $f$: '{e}'" in return Failed }
do! File.WriteAllTextAsync (t.get_last_status, a.ToString ())
}
type command_failed =
{ exit_code : int; file_name: string; arguments: string; stdout: string; stderr: string }
exception Command_failed of command_failed
/// <summary> <code>procrun</code> is plumber process run </summary>
let procrun fname arglist =
let info = new ProcessStartInfo (
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
FileName = fname,
Arguments = String.concat " " arglist )
let proc = new Process (StartInfo = info, EnableRaisingEvents = true)
if not <| proc.Start () then
failwith "panic: current context does not support creating subprocess"
async {
let! stdout = Async.AwaitTask @@ proc.StandardOutput.ReadToEndAsync ()
let! stderr = Async.AwaitTask @@ proc.StandardError.ReadToEndAsync ()
// do! Stdlib.Async.unit <| proc.WaitForExitAsync ()
proc.WaitForExit () // XXX(kinten) KILL ME
if proc.ExitCode <> 0 then
raise @@ Command_failed { exit_code = proc.ExitCode; file_name = info.FileName; arguments = info.Arguments; stdout = stdout; stderr = stderr }
return ()
}
let arduino_cli = "arduino-cli"
let fqbn name = ["--fqbn"; name]
let port name = ["--port"; name]
let help = ["--help"]
/// <usage>run this in an event-loop guilt-free.</usage>
let compile m fqbn_ port_ code =
memo code m @@ async {
use! code_tmpdir = Stdlib.IO.Path.GetTempFileDir ()
Debug.Log $"Frduino.Prgmr: compile created tempdir at '{code_tmpdir.name}'"
use! code_dir = Stdlib.IO.Path.CreateDirectory @@ code_tmpdir.name + "/main"
do! System.IO.File.WriteAllTextAsync (code_dir.name + "/main.ino", code) |> Stdlib.Async.unit
Debug.Log $"Frduino.Prgmr: wrote file"
do! procrun arduino_cli @@ help
Debug.Log $"Frduino.Prgmr: verified version"
do! procrun arduino_cli @@ "compile" :: fqbn fqbn_ @ [code_dir.name]
do! procrun arduino_cli @@ "upload" :: port port_ @ fqbn fqbn_ @ [code_dir.name]
Debug.Log "finished compile and upload"
return () }