| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- // Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
- //
- // This Source Code Form is subject to the terms of the MIT License.
- // If a copy of the MIT was not distributed with this file,
- // You can obtain one at https://github.com/gogf/gf.
- package cmd
- import (
- "context"
- "fmt"
- "runtime"
- "strings"
- "github.com/gogf/gf/v2/container/gtype"
- "github.com/gogf/gf/v2/frame/g"
- "github.com/gogf/gf/v2/os/gfile"
- "github.com/gogf/gf/v2/os/gfsnotify"
- "github.com/gogf/gf/v2/os/gproc"
- "github.com/gogf/gf/v2/os/gtime"
- "github.com/gogf/gf/v2/os/gtimer"
- "github.com/gogf/gf/v2/util/gtag"
- "github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
- )
- var (
- Run = cRun{}
- )
- type cRun struct {
- g.Meta `name:"run" usage:"{cRunUsage}" brief:"{cRunBrief}" eg:"{cRunEg}" dc:"{cRunDc}"`
- }
- type cRunApp struct {
- File string // Go run file name.
- Path string // Directory storing built binary.
- Options string // Extra "go run" options.
- Args string // Custom arguments.
- }
- const (
- cRunUsage = `gf run FILE [OPTION]`
- cRunBrief = `running go codes with hot-compiled-like feature`
- cRunEg = `
- gf run main.go
- gf run main.go --args "server -p 8080"
- gf run main.go -mod=vendor
- `
- cRunDc = `
- The "run" command is used for running go codes with hot-compiled-like feature,
- which compiles and runs the go codes asynchronously when codes change.
- `
- cRunFileBrief = `building file path.`
- cRunPathBrief = `output directory path for built binary file. it's "manifest/output" in default`
- cRunExtraBrief = `the same options as "go run"/"go build" except some options as follows defined`
- cRunArgsBrief = `custom arguments for your process`
- )
- var (
- process *gproc.Process
- )
- func init() {
- gtag.Sets(g.MapStrStr{
- `cRunUsage`: cRunUsage,
- `cRunBrief`: cRunBrief,
- `cRunEg`: cRunEg,
- `cRunDc`: cRunDc,
- `cRunFileBrief`: cRunFileBrief,
- `cRunPathBrief`: cRunPathBrief,
- `cRunExtraBrief`: cRunExtraBrief,
- `cRunArgsBrief`: cRunArgsBrief,
- })
- }
- type (
- cRunInput struct {
- g.Meta `name:"run"`
- File string `name:"FILE" arg:"true" brief:"{cRunFileBrief}" v:"required"`
- Path string `name:"path" short:"p" brief:"{cRunPathBrief}" d:"./"`
- Extra string `name:"extra" short:"e" brief:"{cRunExtraBrief}"`
- Args string `name:"args" short:"a" brief:"{cRunArgsBrief}"`
- }
- cRunOutput struct{}
- )
- func (c cRun) Index(ctx context.Context, in cRunInput) (out *cRunOutput, err error) {
- // Necessary check.
- if gproc.SearchBinary("go") == "" {
- mlog.Fatalf(`command "go" not found in your environment, please install golang first to proceed this command`)
- }
- app := &cRunApp{
- File: in.File,
- Path: in.Path,
- Options: in.Extra,
- Args: in.Args,
- }
- dirty := gtype.NewBool()
- _, err = gfsnotify.Add(gfile.RealPath("."), func(event *gfsnotify.Event) {
- if gfile.ExtName(event.Path) != "go" {
- return
- }
- // Variable `dirty` is used for running the changes only one in one second.
- if !dirty.Cas(false, true) {
- return
- }
- // With some delay in case of multiple code changes in very short interval.
- gtimer.SetTimeout(ctx, 1500*gtime.MS, func(ctx context.Context) {
- defer dirty.Set(false)
- mlog.Printf(`go file changes: %s`, event.String())
- app.Run(ctx)
- })
- })
- if err != nil {
- mlog.Fatal(err)
- }
- go app.Run(ctx)
- select {}
- }
- func (app *cRunApp) Run(ctx context.Context) {
- // Rebuild and run the codes.
- renamePath := ""
- mlog.Printf("build: %s", app.File)
- outputPath := gfile.Join(app.Path, gfile.Name(app.File))
- if runtime.GOOS == "windows" {
- outputPath += ".exe"
- if gfile.Exists(outputPath) {
- renamePath = outputPath + "~"
- if err := gfile.Rename(outputPath, renamePath); err != nil {
- mlog.Print(err)
- }
- }
- }
- // In case of `pipe: too many open files` error.
- // Build the app.
- buildCommand := fmt.Sprintf(
- `go build -o %s %s %s`,
- outputPath,
- app.Options,
- app.File,
- )
- mlog.Print(buildCommand)
- result, err := gproc.ShellExec(ctx, buildCommand)
- if err != nil {
- mlog.Printf("build error: \n%s%s", result, err.Error())
- return
- }
- // Kill the old process if build successfully.
- if process != nil {
- if err := process.Kill(); err != nil {
- mlog.Debugf("kill process error: %s", err.Error())
- //return
- }
- }
- // Run the binary file.
- runCommand := fmt.Sprintf(`%s %s`, outputPath, app.Args)
- mlog.Print(runCommand)
- if runtime.GOOS == "windows" {
- // Special handling for windows platform.
- // DO NOT USE "cmd /c" command.
- process = gproc.NewProcess(outputPath, strings.Fields(app.Args))
- } else {
- process = gproc.NewProcessCmd(runCommand, nil)
- }
- if pid, err := process.Start(ctx); err != nil {
- mlog.Printf("build running error: %s", err.Error())
- } else {
- mlog.Printf("build running pid: %d", pid)
- }
- }
|