1
2
3
4
18
19 package main
20
21 import (
22 "io"
23 "log"
24 "net"
25 "os"
26 "os/exec"
27 "path"
28 "path/filepath"
29 "strings"
30 "sync"
31 "time"
32
33 "github.com/fsnotify/fsnotify"
34 )
35
36
37 func startServer() error {
38 exe, err := os.Executable()
39 if err != nil {
40 return err
41 }
42 args := []string{"-server"}
43 args = append(args, os.Args[1:]...)
44 cmd := exec.Command(exe, args...)
45 log.Printf("starting server: %s", strings.Join(cmd.Args, " "))
46 if err := cmd.Start(); err != nil {
47 return err
48 }
49 if err := cmd.Process.Release(); err != nil {
50 return err
51 }
52 return nil
53 }
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 func runServer() error {
69
70 logFile, err := os.OpenFile(*logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o666)
71 if err != nil {
72 return err
73 }
74 defer logFile.Close()
75 log.SetOutput(logFile)
76
77
78
79
80 os.Remove(*socketPath)
81 ln, err := net.Listen("unix", *socketPath)
82 if err != nil {
83 return err
84 }
85 uln := ln.(*net.UnixListener)
86 uln.SetUnlinkOnClose(true)
87 defer ln.Close()
88 if err := uln.SetDeadline(time.Now().Add(*serverTimeout)); err != nil {
89 return err
90 }
91 log.Printf("started server with pid %d", os.Getpid())
92
93
94 restoreBuildFilesInRepo()
95
96
97 cancelWatch, err := watchDir(".", recordWrite)
98 isWatching := err == nil
99 if err != nil {
100 log.Print(err)
101 }
102 if isWatching {
103 defer cancelWatch()
104 }
105
106
107
108 mode := fullMode
109 for {
110 c, err := ln.Accept()
111 if err != nil {
112 if operr, ok := err.(*net.OpError); ok {
113 if operr.Timeout() {
114 return nil
115 }
116 if operr.Temporary() {
117 log.Printf("temporary watch error: %v", err)
118 continue
119 }
120 }
121 return err
122 }
123
124 log.SetOutput(io.MultiWriter(c, logFile))
125 dirs := getAndClearWrittenDirs()
126 for _, dir := range dirs {
127 restoreBuildFilesInDir(dir)
128 }
129 if err := runGazelle(mode, dirs); err != nil {
130 log.Print(err)
131 }
132 log.SetOutput(logFile)
133 c.Close()
134 if isWatching {
135 mode = fastMode
136 }
137 }
138 }
139
140
141
142
143
144 func watchDir(root string, record func(string)) (cancel func(), err error) {
145 w, err := fsnotify.NewWatcher()
146 if err != nil {
147 return nil, err
148 }
149
150 dirs, errs := listDirs(root)
151 for _, err := range errs {
152 log.Print(err)
153 }
154 gitDir := filepath.Join(root, ".git")
155 for _, dir := range dirs {
156 if dir == gitDir {
157 continue
158 }
159 if err := w.Add(dir); err != nil {
160 log.Print(err)
161 }
162 }
163
164 done := make(chan struct{})
165 go func() {
166 for {
167 select {
168 case ev := <-w.Events:
169 if shouldIgnore(ev.Name) {
170 continue
171 }
172 if ev.Op == fsnotify.Create {
173 if st, err := os.Lstat(ev.Name); err != nil {
174 log.Print(err)
175 } else if st.IsDir() {
176 dirs, errs := listDirs(ev.Name)
177 for _, err := range errs {
178 log.Print(err)
179 }
180 for _, dir := range dirs {
181 if err := w.Add(dir); err != nil {
182 log.Print(err)
183 }
184 recordWrite(dir)
185 }
186 }
187 } else {
188 recordWrite(filepath.Dir(ev.Name))
189 }
190 case err := <-w.Errors:
191 log.Print(err)
192 case <-done:
193 if err := w.Close(); err != nil {
194 log.Print(err)
195 }
196 return
197 }
198 }
199 }()
200 return func() { close(done) }, nil
201 }
202
203
204
205 func listDirs(dir string) ([]string, []error) {
206 var dirs []string
207 var errs []error
208 err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
209 if err != nil {
210 errs = append(errs, err)
211 return nil
212 }
213 if info.IsDir() {
214 dirs = append(dirs, path)
215 }
216 return nil
217 })
218 if err != nil {
219 errs = append(errs, err)
220 }
221 return dirs, errs
222 }
223
224
225
226
227 func shouldIgnore(p string) bool {
228 p = strings.TrimPrefix(filepath.ToSlash(p), "./")
229 base := path.Base(p)
230 return strings.HasPrefix(p, "tools/") || base == ".git" || base == "BUILD" || base == "BUILD.bazel"
231 }
232
233 var (
234 dirSetMutex sync.Mutex
235 dirSet = map[string]bool{}
236 )
237
238
239
240 func recordWrite(path string) {
241 dirSetMutex.Lock()
242 defer dirSetMutex.Unlock()
243 dirSet[path] = true
244 }
245
246
247
248 func getAndClearWrittenDirs() []string {
249 dirSetMutex.Lock()
250 defer dirSetMutex.Unlock()
251 dirs := make([]string, 0, len(dirSet))
252 for d := range dirSet {
253 dirs = append(dirs, d)
254 }
255 dirSet = make(map[string]bool)
256 return dirs
257 }
258
View as plain text