250 lines
4.3 KiB
Go
250 lines
4.3 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"hash/fnv"
|
|
"io"
|
|
"io/fs"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/mqu/go-notify"
|
|
)
|
|
|
|
const iconpath = "/data/drive/jan/hotreload/icon.png"
|
|
|
|
var enableNotify = os.Getenv("GOHRL_NO_NOTIFY") == ""
|
|
|
|
func logg(v ...any) {
|
|
spr := fmt.Sprintln(v...)
|
|
fmt.Print("\x1B[32m", time.Now().Format("15:04:05.000"), " \x1B[93m", spr[:len(spr)-1], "\x1B[0m\n")
|
|
}
|
|
|
|
func main() {
|
|
notify.Init("gohrl")
|
|
|
|
name := os.Args[1]
|
|
args := os.Args[2:]
|
|
|
|
cont := true
|
|
|
|
sc := make(chan os.Signal, 1)
|
|
exit := make(chan struct{}, 1)
|
|
change := make(chan struct{}, 1)
|
|
updated := make(chan struct{}, 1)
|
|
|
|
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
|
|
|
|
tdir, err := os.MkdirTemp("", "gohrl-build-*")
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
defer func() {
|
|
logg(os.RemoveAll(tdir))
|
|
}()
|
|
|
|
var changes []string
|
|
go func() {
|
|
hash()
|
|
var ok bool
|
|
|
|
for {
|
|
time.Sleep(time.Second)
|
|
|
|
changes, ok = hash()
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
change <- struct{}{}
|
|
}
|
|
}()
|
|
|
|
// build loop
|
|
go func() {
|
|
//var not = notify.NotificationNew("updating", strings.Join(changes, "\n"), iconpath)
|
|
|
|
change <- struct{}{} // make sure it builds once off the bat
|
|
|
|
for cont {
|
|
<-change
|
|
logg("change detected", changes, "building")
|
|
//not.Update("updating", strings.Join(changes, "\n"), iconpath)
|
|
//not.SetTimeout(500)
|
|
//not.Show()
|
|
|
|
cmd := exec.Command("go", "build", "-o", tdir+"/build", name)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Stdin = os.Stdin
|
|
err := cmd.Run()
|
|
|
|
if err != nil {
|
|
logg("possible error, waiting for next update")
|
|
continue
|
|
}
|
|
|
|
updated <- struct{}{}
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case <-sc:
|
|
return
|
|
case <-updated:
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
|
|
// application run loop
|
|
go func() {
|
|
var not = notify.NotificationNew("app running", "", iconpath)
|
|
|
|
var rapid = 0
|
|
|
|
for cont {
|
|
logg("starting:", name, args)
|
|
|
|
cmd := exec.Command(tdir+"/build", args...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Stdin = os.Stdin
|
|
|
|
kill := func() {
|
|
syscall.Kill(cmd.Process.Pid, syscall.SIGINT)
|
|
|
|
t := time.NewTimer(time.Second * 10)
|
|
|
|
select {
|
|
case <-exit:
|
|
return
|
|
case <-t.C:
|
|
logg("program didn't exit: killing")
|
|
syscall.Kill(cmd.Process.Pid, syscall.SIGTERM)
|
|
}
|
|
}
|
|
|
|
startedAt := time.Now()
|
|
cmd.Start()
|
|
go func() {
|
|
cmd.Wait()
|
|
exit <- struct{}{}
|
|
}()
|
|
|
|
if not != nil && enableNotify {
|
|
not.Update("update applied", strings.Join(changes, "\n"), iconpath)
|
|
not.SetTimeout(1000)
|
|
not.Show()
|
|
}
|
|
|
|
select {
|
|
case <-sc:
|
|
logg("inturrupting")
|
|
cont = false
|
|
kill()
|
|
case <-updated:
|
|
logg("restarting to apply update")
|
|
//not.Update("restarting to apply updates", strings.Join(changes, "\n"), iconpath)
|
|
//not.SetTimeout(1000)
|
|
//not.Show()
|
|
|
|
kill()
|
|
case <-exit:
|
|
logg("program exited on its own...")
|
|
|
|
switch t := time.Since(startedAt); {
|
|
case t < time.Second*3:
|
|
rapid++
|
|
case t > time.Minute:
|
|
rapid = 0
|
|
default:
|
|
rapid--
|
|
}
|
|
|
|
if rapid > 2 {
|
|
multip := rapid * (rapid / 2)
|
|
if enableNotify {
|
|
not.Update("delaying restart due to rapid exiting", fmt.Sprint(time.Second*time.Duration(multip)), iconpath)
|
|
not.Show()
|
|
}
|
|
|
|
timer := time.NewTimer(time.Second * time.Duration(multip))
|
|
select {
|
|
case <-timer.C:
|
|
case <-updated:
|
|
case <-sc:
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
}
|
|
|
|
time.Sleep(time.Second)
|
|
logg("releasing")
|
|
cmd.Process.Release()
|
|
}
|
|
|
|
wg.Done()
|
|
}()
|
|
|
|
wg.Wait()
|
|
}
|
|
|
|
var hashes = make(map[string][]byte)
|
|
|
|
var pt = false
|
|
|
|
func hash() ([]string, bool) {
|
|
dif := false
|
|
var changes = []string{}
|
|
|
|
err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
logg(err)
|
|
}
|
|
if !strings.HasSuffix(d.Name(), ".go") {
|
|
return nil
|
|
}
|
|
|
|
dat, err := os.Open(filepath.Join(path))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer dat.Close()
|
|
|
|
h := fnv.New128()
|
|
_, err = io.Copy(h, dat)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var b []byte
|
|
b = h.Sum(b)
|
|
|
|
if !bytes.Equal(hashes[dat.Name()], b) {
|
|
if pt {
|
|
logg(dat.Name(), "changed, recompiling and reloading")
|
|
changes = append(changes, dat.Name())
|
|
}
|
|
dif = true
|
|
}
|
|
hashes[dat.Name()] = b
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
pt = true
|
|
|
|
return changes, dif
|
|
}
|