Files
nunu/internal/command/run/run_unix.go
2023-06-07 09:04:35 +08:00

217 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//go:build !plan9 && !windows
// +build !plan9,!windows
package run
import (
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/fsnotify/fsnotify"
"github.com/go-nunu/nunu/internal/pkg/helper"
"github.com/spf13/cobra"
"log"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strings"
"syscall"
)
var quit = make(chan os.Signal, 1)
type Run struct {
}
var RunCmd = &cobra.Command{
Use: "run",
Short: "nunu run [main.go path]",
Long: "nunu wire [wire.go path]",
Example: "nunu run cmd/server",
Run: func(cmd *cobra.Command, args []string) {
cmdArgs, programArgs := helper.SplitArgs(cmd, args)
var dir string
if len(cmdArgs) > 0 {
dir = cmdArgs[0]
}
base, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "\033[31mERROR: %s\033[m\n", err)
return
}
if dir == "" {
// find the directory containing the cmd/*
cmdPath, err := findCMD(base)
if err != nil {
fmt.Fprintf(os.Stderr, "\033[31mERROR: %s\033[m\n", err)
return
}
switch len(cmdPath) {
case 0:
fmt.Fprintf(os.Stderr, "\033[31mERROR: %s\033[m\n", "The cmd directory cannot be found in the current directory")
return
case 1:
for _, v := range cmdPath {
dir = v
}
default:
var cmdPaths []string
for k := range cmdPath {
cmdPaths = append(cmdPaths, k)
}
prompt := &survey.Select{
Message: "Which directory do you want to run?",
Options: cmdPaths,
PageSize: 10,
}
e := survey.AskOne(prompt, &dir)
if e != nil || dir == "" {
return
}
dir = cmdPath[dir]
}
}
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
watch(dir, programArgs)
},
}
func watch(dir string, programArgs []string) {
// Listening file path
watchPath := "./"
// Create a new file watcher
watcher, err := fsnotify.NewWatcher()
if err != nil {
fmt.Println("Error:", err)
return
}
defer watcher.Close()
// Add files to watcher
err = filepath.Walk(watchPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && filepath.Ext(info.Name()) != ".log" && info.Name() != ".git" {
err = watcher.Add(path)
if err != nil {
fmt.Println("Error:", err)
}
}
return nil
})
if err != nil {
fmt.Println("Error:", err)
return
}
cmd := start(dir, programArgs)
// Loop listening file modification
for {
select {
case <-quit:
err = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
if err != nil {
log.Println("server exiting...", err)
return
}
log.Println("server exiting...")
os.Exit(0)
case event := <-watcher.Events:
// The file has been modified or created
if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create {
fmt.Println("File modified:", event.Name)
err = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
if err != nil {
fmt.Println("Stopping program failed", err)
return
}
fmt.Println("Program restart in progress...")
cmd = start(dir, programArgs)
}
case err := <-watcher.Errors:
fmt.Println("Error:", err)
}
}
}
func start(dir string, programArgs []string) *exec.Cmd {
cmd := exec.Command("go", append([]string{"run", dir}, programArgs...)...)
// Set a new process group to kill all child processes when the program exits
if runtime.GOOS == "windows" {
cmd.SysProcAttr = &syscall.SysProcAttr{}
} else {
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
fmt.Println("cmd run failed")
}
fmt.Println("Program started")
return cmd
}
func findCMD(base string) (map[string]string, error) {
wd, err := os.Getwd()
if err != nil {
return nil, err
}
if !strings.HasSuffix(wd, "/") {
wd += "/"
}
var root bool
next := func(dir string) (map[string]string, error) {
cmdPath := make(map[string]string)
err = filepath.Walk(dir, func(walkPath string, info os.FileInfo, err error) error {
// multi level directory is not allowed under the cmdPath directory, so it is judged that the path ends with cmdPath.
if strings.HasSuffix(walkPath, "cmd") {
paths, err := os.ReadDir(walkPath)
if err != nil {
return err
}
for _, fileInfo := range paths {
if fileInfo.IsDir() {
abs := filepath.Join(walkPath, fileInfo.Name())
cmdPath[strings.TrimPrefix(abs, wd)] = abs
}
}
return nil
}
if info.Name() == "go.mod" {
root = true
}
return nil
})
return cmdPath, err
}
for i := 0; i < 5; i++ {
tmp := base
cmd, err := next(tmp)
if err != nil {
return nil, err
}
if len(cmd) > 0 {
return cmd, nil
}
if root {
break
}
_ = filepath.Join(base, "..")
}
return map[string]string{"": base}, nil
}