You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

143 lines
3.1KB

  1. package main
  2. import (
  3. "fmt"
  4. "go/build"
  5. "os"
  6. "os/exec"
  7. "os/signal"
  8. "path/filepath"
  9. "regexp"
  10. "runtime"
  11. "syscall"
  12. log "github.com/Sirupsen/logrus"
  13. "github.com/codegangsta/cli"
  14. "github.com/goanywhere/rex/internal"
  15. "github.com/goanywhere/rex/livereload"
  16. "github.com/goanywhere/cmd"
  17. "github.com/goanywhere/env"
  18. "github.com/goanywhere/fs"
  19. )
  20. var (
  21. port int
  22. watchList = regexp.MustCompile(`\.(go|html|atom|rss|xml)$`)
  23. )
  24. type app struct {
  25. dir string
  26. binary string
  27. args []string
  28. task string // script for npm.
  29. }
  30. // build compiles the application into rex-bin executable
  31. // to run & optionally compiles static assets using npm.
  32. func (self *app) build() {
  33. var done = make(chan bool)
  34. cmd.Loading(done)
  35. // * try build the application into rex-bin(.exe)
  36. command := exec.Command("go", "build", "-o", self.binary)
  37. command.Dir = self.dir
  38. if e := command.Run(); e != nil {
  39. log.Fatalf("Failed to compile the application: %v", e)
  40. }
  41. done <- true
  42. }
  43. // run executes the runnerable executable under package binary root.
  44. func (self *app) run() (gorun chan bool) {
  45. gorun = make(chan bool)
  46. go func() {
  47. var proc *os.Process
  48. for start := range gorun {
  49. if proc != nil {
  50. // try soft kill before hard one.
  51. if err := proc.Signal(os.Interrupt); err != nil {
  52. proc.Kill()
  53. }
  54. proc.Wait()
  55. }
  56. if !start {
  57. continue
  58. }
  59. command := exec.Command(self.binary, fmt.Sprintf("--port=%d", port))
  60. command.Dir = self.dir
  61. command.Stdout = os.Stdout
  62. command.Stderr = os.Stderr
  63. if err := command.Start(); err != nil {
  64. log.Fatalf("Failed to start the process: %v\n", err)
  65. }
  66. proc = command.Process
  67. }
  68. }()
  69. return
  70. }
  71. func (self *app) rerun(gorun chan bool) {
  72. self.build()
  73. livereload.Reload()
  74. gorun <- true
  75. }
  76. // Starts activates the application server along with
  77. // a daemon watcher for monitoring the files's changes.
  78. func (self *app) Start() {
  79. // ctrl-c: listen removes binary package when application stopped.
  80. channel := make(chan os.Signal, 2)
  81. signal.Notify(channel, os.Interrupt, syscall.SIGTERM)
  82. go func() {
  83. <-channel
  84. // remove the binary package on stop.
  85. os.Remove(self.binary)
  86. os.Exit(1)
  87. }()
  88. // start waiting the signal to start running.
  89. var gorun = self.run()
  90. self.build()
  91. gorun <- true
  92. watcher := fs.NewWatcher(self.dir)
  93. log.Infof("Start watching: %s", self.dir)
  94. watcher.Add(watchList, func(filename string) {
  95. relpath, _ := filepath.Rel(self.dir, filename)
  96. log.Infof("Changes on %s detected", relpath)
  97. self.rerun(gorun)
  98. })
  99. watcher.Start()
  100. }
  101. // Run creates an executable application package with livereload supports.
  102. func Run(ctx *cli.Context) {
  103. port = ctx.Int("port")
  104. if len(ctx.Args()) == 1 {
  105. cwd = ctx.Args()[0]
  106. }
  107. if abspath, err := filepath.Abs(cwd); err == nil {
  108. env.Set(internal.BaseDir, abspath)
  109. } else {
  110. log.Fatalf("Failed to retrieve the directory: %v", err)
  111. }
  112. pkg, err := build.ImportDir(cwd, build.AllowBinary)
  113. if err != nil || pkg.Name != "main" {
  114. log.Fatalf("No buildable Go source files found")
  115. }
  116. app := new(app)
  117. app.dir = cwd
  118. app.binary = filepath.Join(os.TempDir(), "rex-bin")
  119. if runtime.GOOS == "windows" {
  120. app.binary += ".exe"
  121. }
  122. app.task = ctx.String("task")
  123. app.Start()
  124. }