cmd_run.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
  2. //
  3. // This Source Code Form is subject to the terms of the MIT License.
  4. // If a copy of the MIT was not distributed with this file,
  5. // You can obtain one at https://github.com/gogf/gf.
  6. package cmd
  7. import (
  8. "context"
  9. "fmt"
  10. "runtime"
  11. "strings"
  12. "github.com/gogf/gf/v2/container/gtype"
  13. "github.com/gogf/gf/v2/frame/g"
  14. "github.com/gogf/gf/v2/os/gfile"
  15. "github.com/gogf/gf/v2/os/gfsnotify"
  16. "github.com/gogf/gf/v2/os/gproc"
  17. "github.com/gogf/gf/v2/os/gtime"
  18. "github.com/gogf/gf/v2/os/gtimer"
  19. "github.com/gogf/gf/v2/util/gtag"
  20. "github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
  21. )
  22. var (
  23. Run = cRun{}
  24. )
  25. type cRun struct {
  26. g.Meta `name:"run" usage:"{cRunUsage}" brief:"{cRunBrief}" eg:"{cRunEg}" dc:"{cRunDc}"`
  27. }
  28. type cRunApp struct {
  29. File string // Go run file name.
  30. Path string // Directory storing built binary.
  31. Options string // Extra "go run" options.
  32. Args string // Custom arguments.
  33. }
  34. const (
  35. cRunUsage = `gf run FILE [OPTION]`
  36. cRunBrief = `running go codes with hot-compiled-like feature`
  37. cRunEg = `
  38. gf run main.go
  39. gf run main.go --args "server -p 8080"
  40. gf run main.go -mod=vendor
  41. `
  42. cRunDc = `
  43. The "run" command is used for running go codes with hot-compiled-like feature,
  44. which compiles and runs the go codes asynchronously when codes change.
  45. `
  46. cRunFileBrief = `building file path.`
  47. cRunPathBrief = `output directory path for built binary file. it's "manifest/output" in default`
  48. cRunExtraBrief = `the same options as "go run"/"go build" except some options as follows defined`
  49. cRunArgsBrief = `custom arguments for your process`
  50. )
  51. var (
  52. process *gproc.Process
  53. )
  54. func init() {
  55. gtag.Sets(g.MapStrStr{
  56. `cRunUsage`: cRunUsage,
  57. `cRunBrief`: cRunBrief,
  58. `cRunEg`: cRunEg,
  59. `cRunDc`: cRunDc,
  60. `cRunFileBrief`: cRunFileBrief,
  61. `cRunPathBrief`: cRunPathBrief,
  62. `cRunExtraBrief`: cRunExtraBrief,
  63. `cRunArgsBrief`: cRunArgsBrief,
  64. })
  65. }
  66. type (
  67. cRunInput struct {
  68. g.Meta `name:"run"`
  69. File string `name:"FILE" arg:"true" brief:"{cRunFileBrief}" v:"required"`
  70. Path string `name:"path" short:"p" brief:"{cRunPathBrief}" d:"./"`
  71. Extra string `name:"extra" short:"e" brief:"{cRunExtraBrief}"`
  72. Args string `name:"args" short:"a" brief:"{cRunArgsBrief}"`
  73. }
  74. cRunOutput struct{}
  75. )
  76. func (c cRun) Index(ctx context.Context, in cRunInput) (out *cRunOutput, err error) {
  77. // Necessary check.
  78. if gproc.SearchBinary("go") == "" {
  79. mlog.Fatalf(`command "go" not found in your environment, please install golang first to proceed this command`)
  80. }
  81. app := &cRunApp{
  82. File: in.File,
  83. Path: in.Path,
  84. Options: in.Extra,
  85. Args: in.Args,
  86. }
  87. dirty := gtype.NewBool()
  88. _, err = gfsnotify.Add(gfile.RealPath("."), func(event *gfsnotify.Event) {
  89. if gfile.ExtName(event.Path) != "go" {
  90. return
  91. }
  92. // Variable `dirty` is used for running the changes only one in one second.
  93. if !dirty.Cas(false, true) {
  94. return
  95. }
  96. // With some delay in case of multiple code changes in very short interval.
  97. gtimer.SetTimeout(ctx, 1500*gtime.MS, func(ctx context.Context) {
  98. defer dirty.Set(false)
  99. mlog.Printf(`go file changes: %s`, event.String())
  100. app.Run(ctx)
  101. })
  102. })
  103. if err != nil {
  104. mlog.Fatal(err)
  105. }
  106. go app.Run(ctx)
  107. select {}
  108. }
  109. func (app *cRunApp) Run(ctx context.Context) {
  110. // Rebuild and run the codes.
  111. renamePath := ""
  112. mlog.Printf("build: %s", app.File)
  113. outputPath := gfile.Join(app.Path, gfile.Name(app.File))
  114. if runtime.GOOS == "windows" {
  115. outputPath += ".exe"
  116. if gfile.Exists(outputPath) {
  117. renamePath = outputPath + "~"
  118. if err := gfile.Rename(outputPath, renamePath); err != nil {
  119. mlog.Print(err)
  120. }
  121. }
  122. }
  123. // In case of `pipe: too many open files` error.
  124. // Build the app.
  125. buildCommand := fmt.Sprintf(
  126. `go build -o %s %s %s`,
  127. outputPath,
  128. app.Options,
  129. app.File,
  130. )
  131. mlog.Print(buildCommand)
  132. result, err := gproc.ShellExec(ctx, buildCommand)
  133. if err != nil {
  134. mlog.Printf("build error: \n%s%s", result, err.Error())
  135. return
  136. }
  137. // Kill the old process if build successfully.
  138. if process != nil {
  139. if err := process.Kill(); err != nil {
  140. mlog.Debugf("kill process error: %s", err.Error())
  141. //return
  142. }
  143. }
  144. // Run the binary file.
  145. runCommand := fmt.Sprintf(`%s %s`, outputPath, app.Args)
  146. mlog.Print(runCommand)
  147. if runtime.GOOS == "windows" {
  148. // Special handling for windows platform.
  149. // DO NOT USE "cmd /c" command.
  150. process = gproc.NewProcess(outputPath, strings.Fields(app.Args))
  151. } else {
  152. process = gproc.NewProcessCmd(runCommand, nil)
  153. }
  154. if pid, err := process.Start(ctx); err != nil {
  155. mlog.Printf("build running error: %s", err.Error())
  156. } else {
  157. mlog.Printf("build running pid: %d", pid)
  158. }
  159. }