5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-02 20:03:01 +08:00

[v2] Improvements to the dev command (#1510)

* [v2, darwin] Fix nullreference exception when dev watcher failed to start and the user stopped wails dev with ctrl-c

* [v2] Improve starting of dev watcher command

Wails dev now also fails if the dev watcher could not be started. Since
the dev watcher is used to start e.g. vite to serve the frontend, it does
not make much sense to let Wails dev be running if the dev
watcher failed to start.

* [v2] Improve killing and cleanup of debug binary

- Make sure to kill the debug process in all cases even if an error occured.
- Make sure to kill the last started binary process
This commit is contained in:
stffabi 2022-06-30 12:13:06 +02:00 committed by GitHub
parent 3e42d2da05
commit 41d1bf3d10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 70 additions and 44 deletions

View File

@ -2,6 +2,7 @@ package dev
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"net" "net"
@ -168,8 +169,6 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
buildOptions.Logger = logger buildOptions.Logger = logger
buildOptions.UserTags = internal.ParseUserTags(flags.tags) buildOptions.UserTags = internal.ParseUserTags(flags.tags)
var debugBinaryProcess *process.Process = nil
// Setup signal handler // Setup signal handler
quitChannel := make(chan os.Signal, 1) quitChannel := make(chan os.Signal, 1)
signal.Notify(quitChannel, os.Interrupt, os.Kill, syscall.SIGTERM) signal.Notify(quitChannel, os.Interrupt, os.Kill, syscall.SIGTERM)
@ -177,19 +176,23 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// Do initial build // Do initial build
logger.Println("Building application for development...") logger.Println("Building application for development...")
newProcess, appBinary, err := restartApp(buildOptions, debugBinaryProcess, flags, exitCodeChannel) debugBinaryProcess, appBinary, err := restartApp(buildOptions, nil, flags, exitCodeChannel)
if err != nil { if err != nil {
return err return err
} }
if newProcess != nil { defer func() {
debugBinaryProcess = newProcess if err := killProcessAndCleanupBinary(debugBinaryProcess, appBinary); err != nil {
} LogDarkYellow("Unable to kill process and cleanup binary: %s", err)
}
}()
// frontend:dev:watcher command. // frontend:dev:watcher command.
if command := projectConfig.DevWatcherCommand; command != "" { if command := projectConfig.DevWatcherCommand; command != "" {
var devCommandWaitGroup sync.WaitGroup closer, err := runFrontendDevWatcherCommand(cwd, command)
closer := runFrontendDevWatcherCommand(cwd, command, &devCommandWaitGroup) if err != nil {
defer closer(&devCommandWaitGroup) return err
}
defer closer()
} }
// open browser // open browser
@ -221,22 +224,17 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
LogGreen("Using reload debounce setting of %d milliseconds", flags.debounceMS) LogGreen("Using reload debounce setting of %d milliseconds", flags.debounceMS)
// Watch for changes and trigger restartApp() // Watch for changes and trigger restartApp()
doWatcherLoop(buildOptions, debugBinaryProcess, flags, watcher, exitCodeChannel, quitChannel, devServerURL) debugBinaryProcess = doWatcherLoop(buildOptions, debugBinaryProcess, flags, watcher, exitCodeChannel, quitChannel, devServerURL)
// Kill the current program if running // Kill the current program if running and remove dev binary
if debugBinaryProcess != nil { if err := killProcessAndCleanupBinary(debugBinaryProcess, appBinary); err != nil {
err := debugBinaryProcess.Kill()
if err != nil {
return err
}
}
// Remove dev binary
err = os.Remove(appBinary)
if err != nil {
return err return err
} }
// Reset the process and the binary so the defer knows about it and is a nop.
debugBinaryProcess = nil
appBinary = ""
LogGreen("Development mode exited") LogGreen("Development mode exited")
return nil return nil
@ -244,6 +242,22 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
return nil return nil
} }
func killProcessAndCleanupBinary(process *process.Process, binary string) error {
if process != nil && process.Running {
if err := process.Kill(); err != nil {
return err
}
}
if binary != "" {
err := os.Remove(binary)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
}
return nil
}
func syncGoModVersion(cwd string) error { func syncGoModVersion(cwd string) error {
gomodFilename := filepath.Join(cwd, "go.mod") gomodFilename := filepath.Join(cwd, "go.mod")
gomodData, err := os.ReadFile(gomodFilename) gomodData, err := os.ReadFile(gomodFilename)
@ -388,35 +402,39 @@ func loadAndMergeProjectConfig(cwd string, flags *devFlags) (*project.Project, e
} }
// runFrontendDevWatcherCommand will run the `frontend:dev:watcher` command if it was given, ex- `npm run dev` // runFrontendDevWatcherCommand will run the `frontend:dev:watcher` command if it was given, ex- `npm run dev`
func runFrontendDevWatcherCommand(cwd string, devCommand string, wg *sync.WaitGroup) func(group *sync.WaitGroup) { func runFrontendDevWatcherCommand(cwd string, devCommand string) (func(), error) {
LogGreen("Running frontend dev watcher command: '%s'", devCommand)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
dir := filepath.Join(cwd, "frontend") dir := filepath.Join(cwd, "frontend")
cmdSlice := strings.Split(devCommand, " ") cmdSlice := strings.Split(devCommand, " ")
wg.Add(1)
cmd := exec.CommandContext(ctx, cmdSlice[0], cmdSlice[1:]...) cmd := exec.CommandContext(ctx, cmdSlice[0], cmdSlice[1:]...)
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Dir = dir cmd.Dir = dir
setParentGID(cmd) setParentGID(cmd)
go func(ctx context.Context, devCommand string, cwd string, wg *sync.WaitGroup) { if err := cmd.Start(); err != nil {
err := cmd.Run() cancel()
if err != nil { return nil, fmt.Errorf("Unable to start frontend DevWatcher: %w", err)
}
LogGreen("Running frontend DevWatcher command: '%s'", devCommand)
var wg sync.WaitGroup
wg.Add(1)
go func() {
if err := cmd.Wait(); err != nil {
if err.Error() != "exit status 1" { if err.Error() != "exit status 1" {
LogRed("Error from '%s': %s", devCommand, err.Error()) LogRed("Error from DevWatcher '%s': %s", devCommand, err.Error())
} }
} }
LogGreen("Dev command exited!") LogGreen("DevWatcher command exited!")
wg.Done() wg.Done()
}(ctx, devCommand, cwd, wg) }()
return func(wg *sync.WaitGroup) { return func() {
killProc(cmd, devCommand) killProc(cmd, devCommand)
LogGreen("Dev command killed!") LogGreen("DevWatcher command killed!")
cancel() cancel()
wg.Wait() wg.Wait()
} }, nil
} }
// initialiseWatcher creates the project directory watcher that will trigger recompile // initialiseWatcher creates the project directory watcher that will trigger recompile
@ -463,8 +481,13 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process
appBinary, err := build.Build(buildOptions) appBinary, err := build.Build(buildOptions)
println() println()
if err != nil { if err != nil {
LogRed("Build error - continuing to run current version") LogRed("Build error - " + err.Error())
LogDarkYellow(err.Error())
msg := "Continuing to run current version"
if debugBinaryProcess == nil {
msg = "No version running, build will be retriggered as soon as changes have been detected"
}
LogDarkYellow(msg)
return nil, "", nil return nil, "", nil
} }
@ -510,12 +533,8 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process
} }
// doWatcherLoop is the main watch loop that runs while dev is active // doWatcherLoop is the main watch loop that runs while dev is active
func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Process, flags devFlags, watcher *fsnotify.Watcher, exitCodeChannel chan int, quitChannel chan os.Signal, devServerURL *url.URL) { func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Process, flags devFlags, watcher *fsnotify.Watcher, exitCodeChannel chan int, quitChannel chan os.Signal, devServerURL *url.URL) *process.Process {
// Main Loop // Main Loop
var (
err error
newBinaryProcess *process.Process
)
var extensionsThatTriggerARebuild = sliceToMap(strings.Split(flags.extensions, ",")) var extensionsThatTriggerARebuild = sliceToMap(strings.Split(flags.extensions, ","))
var dirsThatTriggerAReload []string var dirsThatTriggerAReload []string
for _, dir := range strings.Split(flags.reloadDirs, ",") { for _, dir := range strings.Split(flags.reloadDirs, ",") {
@ -601,7 +620,7 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc
rebuild = false rebuild = false
LogGreen("[Rebuild triggered] files updated") LogGreen("[Rebuild triggered] files updated")
// Try and build the app // Try and build the app
newBinaryProcess, _, err = restartApp(buildOptions, debugBinaryProcess, flags, exitCodeChannel) newBinaryProcess, _, err := restartApp(buildOptions, debugBinaryProcess, flags, exitCodeChannel)
if err != nil { if err != nil {
LogRed("Error during build: %s", err.Error()) LogRed("Error during build: %s", err.Error())
continue continue
@ -647,7 +666,7 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc
} }
if reload { if reload {
reload = false reload = false
_, err = http.Get(reloadURL) _, err := http.Get(reloadURL)
if err != nil { if err != nil {
LogRed("Error during refresh: %s", err.Error()) LogRed("Error during refresh: %s", err.Error())
} }
@ -657,6 +676,7 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc
quit = true quit = true
} }
} }
return debugBinaryProcess
} }
func joinPath(url *url.URL, subPath string) string { func joinPath(url *url.URL, subPath string) string {

View File

@ -6,6 +6,8 @@ package dev
import ( import (
"os/exec" "os/exec"
"syscall" "syscall"
"golang.org/x/sys/unix"
) )
func setParentGID(cmd *exec.Cmd) { func setParentGID(cmd *exec.Cmd) {
@ -15,6 +17,10 @@ func setParentGID(cmd *exec.Cmd) {
} }
func killProc(cmd *exec.Cmd, devCommand string) { func killProc(cmd *exec.Cmd, devCommand string) {
if cmd == nil || cmd.Process == nil {
return
}
// Experiencing the same issue on macOS BigSur // Experiencing the same issue on macOS BigSur
// I'm using Vite, but I would imagine this could be an issue with Node (npm) in general // I'm using Vite, but I would imagine this could be an issue with Node (npm) in general
// Also, after several edit/rebuild cycles any abnormal shutdown (crash or CTRL-C) may still leave Node running // Also, after several edit/rebuild cycles any abnormal shutdown (crash or CTRL-C) may still leave Node running
@ -22,7 +28,7 @@ func killProc(cmd *exec.Cmd, devCommand string) {
// Not tested on *nix // Not tested on *nix
pgid, err := syscall.Getpgid(cmd.Process.Pid) pgid, err := syscall.Getpgid(cmd.Process.Pid)
if err == nil { if err == nil {
err := syscall.Kill(-pgid, 15) // note the minus sign err := syscall.Kill(-pgid, unix.SIGTERM) // note the minus sign
if err != nil { if err != nil {
LogRed("Error from '%s' when attempting to kill the process: %s", devCommand, err.Error()) LogRed("Error from '%s' when attempting to kill the process: %s", devCommand, err.Error())
} }