/
main.go
149 lines (124 loc) · 3.68 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Package run has two main functions:
// - work as a task runner, watching files and
// rebuilding them if necessary;
// - works as a proxy server, that runs a user application,
// proxies all requests to it, and shows detailed
// error messages if needed (TODO).
package run
import (
"os"
"os/signal"
"path/filepath"
"syscall"
"github.com/goaltools/goal/internal/log"
"github.com/goaltools/goal/internal/watcher"
"github.com/goaltools/goal/utils/tool"
"github.com/conveyer/importpath"
"gopkg.in/fsnotify.v1"
)
// ConfigFile is a name of the file that is located at the
// root of user project and describes what the test runner should do.
var ConfigFile = "goal.yml"
// Handler is an instance of "run" subcommand (tool).
var Handler = tool.Handler{
Run: main,
Name: "run",
Usage: "{path}",
Info: "start a task runner",
Desc: `Run is a watcher and task runner. It uses goal.yml
file at the root of your project to find out what it should watch
and how to build / start your application.
Tasks of "init" section are run first, but only once per starting "goal run"
command. "watch" section is to inform that when some files in the specified
directories are modified, some tasks are expected to be executed.
`,
}
var (
notify = make(chan os.Signal, 1)
restart = make(chan bool, 1)
)
// main is an entry point of the "run" subcommand (tool).
func main(hs []tool.Handler, i int, args tool.Data) {
// The first argument in the list is a path.
// If it's missing use an empty string instead.
p := args.GetDefault(0, "")
// Determine import path and absolute path of the project to run.
imp, err := importpath.Clean(p)
if err != nil {
log.Error.Panic(err)
}
dir, err := importpath.ToPath(imp)
if err != nil {
log.Error.Panic(err)
}
// Prepare a path of configuration file.
cf := filepath.Join(dir, ConfigFile)
// Start a user tasks runner and instances controller.
go instanceController()
// Start a configuration file watcher.
go configDaemon(imp, cf)
// Show user friendly errors and terminate subprograms
// in case of panics.
defer func() {
channel <- message{
action: "exit",
}
<-stopped
log.Trace.Panicln("Application has been terminated.")
}()
// Execute all commands from the requested directory.
curr, _ := os.Getwd()
os.Chdir(dir) // pushd
defer func() {
// Going back to the initial directory.
os.Chdir(curr) // popd
}()
// Load the configuration.
reloadConfig()
// Cleaning up after we are done.
signal.Notify(notify, os.Interrupt, syscall.SIGTERM)
<-notify
}
func configDaemon(imp, file string) {
var watchers []*fsnotify.Watcher
// closeWatchers is iterating over available watchers
// and closes them.
closeWatchers := func() {
for i := range watchers {
watchers[i].Close()
}
watchers = []*fsnotify.Watcher{}
}
defer closeWatchers() // Close watchers when we are done.
for {
// Wait till we are asked to reload the config file.
<-restart
// Closing old watchers to create new ones.
closeWatchers()
// Make sure configuration file does exist.
_, err := os.Stat(file)
if err != nil || os.IsNotExist(err) {
log.Error.Printf(
`Are you sure "%s" is a path of goal project?
"%s" file is missing.`, imp, file,
)
notify <- syscall.SIGTERM
return
}
// Parsing configuration file and extracting the values
// we need.
log.Trace.Printf(`Starting to parse "%s"...`, file)
c := parseConf(file)
// Start init tasks.
c.init()
// Start watching the requested directories.
w := watcher.NewType()
watchers = append(watchers, w.ListenFile("./"+ConfigFile, reloadConfig))
for pattern := range c.watch {
watchers = append(watchers, w.Listen(pattern, c.watch[pattern]))
}
}
}
func reloadConfig() {
restart <- true
}