192 lines
5.2 KiB
Go
192 lines
5.2 KiB
Go
package main
|
|
|
|
// Rebuilding and refreshing, and the difference between them.
|
|
//
|
|
// r read the data again in this panel. Nothing is closed, nothing moves.
|
|
// R rebuild every workspace and pane from the project file: close what the
|
|
// layout owns, then build it again in order.
|
|
//
|
|
// A rebuild must survive the panel that started it, because that panel is one of
|
|
// the things being replaced. So the rebuild runs detached, in its own session,
|
|
// with its output in a log the Repair workspace can show.
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
func rebuildLogPath() string {
|
|
return filepath.Join(os.TempDir(), "paperclip-harness-rebuild.log")
|
|
}
|
|
|
|
// startRebuild launches bin/board --replace for this folder and returns at once.
|
|
func startRebuild(folder string) error {
|
|
board := filepath.Join(exeDir(), "board")
|
|
if _, err := os.Stat(board); err != nil {
|
|
return fmt.Errorf("no bin/board next to the program (%s)", board)
|
|
}
|
|
log, err := os.Create(rebuildLogPath())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(log, "rebuild started %s\n", time.Now().Format("2006-01-02 15:04:05"))
|
|
|
|
cmd := exec.Command(board, "--folder", folder, "--replace")
|
|
cmd.Stdout = log
|
|
cmd.Stderr = log
|
|
cmd.Stdin = nil
|
|
// Its own session, so closing this pane does not take the rebuild with it.
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
|
if err := cmd.Start(); err != nil {
|
|
log.Close()
|
|
return err
|
|
}
|
|
go func() {
|
|
cmd.Wait()
|
|
fmt.Fprintf(log, "rebuild finished %s\n", time.Now().Format("15:04:05"))
|
|
log.Close()
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
func exeDir() string {
|
|
p, err := os.Executable()
|
|
if err != nil {
|
|
return "."
|
|
}
|
|
return filepath.Dir(p)
|
|
}
|
|
|
|
// rebuildLogTail returns the end of the last rebuild, for the Repair workspace.
|
|
func rebuildLogTail(lines int) []string {
|
|
b, err := os.ReadFile(rebuildLogPath())
|
|
if err != nil {
|
|
return []string{"", " no rebuild has been run since this machine started"}
|
|
}
|
|
all := splitLines(string(b))
|
|
start := max(0, len(all)-lines)
|
|
return all[start:]
|
|
}
|
|
|
|
func splitLines(s string) []string {
|
|
var out []string
|
|
cur := ""
|
|
for _, r := range s {
|
|
if r == '\n' {
|
|
out = append(out, cur)
|
|
cur = ""
|
|
continue
|
|
}
|
|
cur += string(r)
|
|
}
|
|
if cur != "" {
|
|
out = append(out, cur)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// mergeLogPath is where a merge writes what it did, so a panel can show it
|
|
// without the person opening a shell or remembering a command.
|
|
func mergeLogPath() string {
|
|
return filepath.Join(os.TempDir(), "paperclip-harness-merge.log")
|
|
}
|
|
|
|
// startMerge runs bin/merge for one task, detached, and returns at once. The
|
|
// Work workspace tails the log; the merge itself takes a lock, so starting one
|
|
// twice by accident is refused rather than doubled.
|
|
func startMerge(folder, identifier string) error {
|
|
merge := filepath.Join(exeDir(), "merge")
|
|
if _, err := os.Stat(merge); err != nil {
|
|
return fmt.Errorf("no bin/merge next to the program (%s)", merge)
|
|
}
|
|
log, err := os.Create(mergeLogPath())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(log, "merge of %s started %s\n", identifier, time.Now().Format("15:04:05"))
|
|
|
|
cmd := exec.Command(merge, identifier)
|
|
cmd.Dir = folder
|
|
cmd.Stdout = log
|
|
cmd.Stderr = log
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
|
if err := cmd.Start(); err != nil {
|
|
log.Close()
|
|
return err
|
|
}
|
|
go func() {
|
|
err := cmd.Wait()
|
|
if err != nil {
|
|
fmt.Fprintf(log, "\nmerge stopped: %v\n", err)
|
|
} else {
|
|
fmt.Fprintf(log, "\nmerge finished %s\n", time.Now().Format("15:04:05"))
|
|
}
|
|
log.Close()
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
// mergeLogTail returns the end of the last merge, for the panel that shows it.
|
|
func mergeLogTail(lines int) []string {
|
|
b, err := os.ReadFile(mergeLogPath())
|
|
if err != nil {
|
|
return []string{"", " no merge has been run from this screen yet"}
|
|
}
|
|
all := splitLines(string(b))
|
|
start := max(0, len(all)-lines)
|
|
return all[start:]
|
|
}
|
|
|
|
// reviewLogPath is where a review started from the interface writes its output.
|
|
func reviewLogPath() string {
|
|
return filepath.Join(os.TempDir(), "paperclip-harness-review.log")
|
|
}
|
|
|
|
// startReview runs bin/review for one task and returns at once. The Review tab
|
|
// follows the log, so a review takes no keypress more than starting it.
|
|
func startReview(folder, identifier string) error {
|
|
review := filepath.Join(exeDir(), "review")
|
|
if _, err := os.Stat(review); err != nil {
|
|
return fmt.Errorf("no bin/review next to the program (%s)", review)
|
|
}
|
|
log, err := os.Create(reviewLogPath())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(log, "review of %s started %s\n", identifier, time.Now().Format("15:04:05"))
|
|
|
|
cmd := exec.Command(review, identifier, "--post")
|
|
cmd.Dir = folder
|
|
cmd.Stdout = log
|
|
cmd.Stderr = log
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
|
if err := cmd.Start(); err != nil {
|
|
log.Close()
|
|
return err
|
|
}
|
|
go func() {
|
|
err := cmd.Wait()
|
|
if err != nil {
|
|
fmt.Fprintf(log, "\nreview stopped: %v\n", err)
|
|
} else {
|
|
fmt.Fprintf(log, "\nreview finished %s\n", time.Now().Format("15:04:05"))
|
|
}
|
|
log.Close()
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
func reviewLogTail(lines int) []string {
|
|
b, err := os.ReadFile(reviewLogPath())
|
|
if err != nil {
|
|
return []string{"", " no review has been run from this screen yet", "", " press v to review this task"}
|
|
}
|
|
all := splitLines(string(b))
|
|
start := max(0, len(all)-lines)
|
|
return all[start:]
|
|
}
|