Skip to content

Commit df33507

Browse files
committed
feat: add watchman integration
1 parent f4bce67 commit df33507

File tree

7 files changed

+391
-25
lines changed

7 files changed

+391
-25
lines changed

'

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package app
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/cdmistman/watchman"
8+
"github.com/cdmistman/watchman/protocol"
9+
"github.com/cdmistman/watchman/protocol/query"
10+
"github.com/rs/zerolog/log"
11+
)
12+
13+
type watchmanState int
14+
15+
const (
16+
uninit watchmanState = iota
17+
initing
18+
errored
19+
ready
20+
)
21+
22+
type Watchman struct {
23+
subscriptions []Subscription
24+
}
25+
26+
type Subscription struct {
27+
updates chan []string
28+
glob string
29+
}
30+
31+
func (w *Watchman) MaybeStart() error {
32+
// TODO: proper error handling
33+
if len(w.subscriptions) == 0 {
34+
return nil
35+
}
36+
37+
globs := []string{}
38+
for _, sub := range w.subscriptions {
39+
globs = append(globs, sub.glob)
40+
}
41+
42+
q := query.Query{
43+
Generators: query.Generators{query.GGlob: globs},
44+
Fields: query.Fields{query.FName},
45+
}
46+
47+
client, err := watchman.Connect()
48+
if err != nil {
49+
return err
50+
}
51+
52+
if !client.HasCapability("glob_generator") {
53+
return fmt.Errorf("watchman does not have the glob_generator capability")
54+
}
55+
56+
cwd, err := os.Getwd()
57+
if err != nil {
58+
return err
59+
}
60+
61+
watch, err := client.AddWatch(cwd)
62+
if err != nil {
63+
return err
64+
}
65+
66+
if watch.RelativePath() != "" {
67+
if !client.HasCapability("relative_root") {
68+
return fmt.Errorf("watchman does not have the relative_root capability but gave a relative root")
69+
}
70+
71+
q.RelativeRoot = watch.RelativePath()
72+
}
73+
74+
if _, err = watch.Subscribe("process-compose", &q); err != nil {
75+
return err
76+
}
77+
78+
go loop(client, w.subscriptions, watch.Root(), watch.RelativePath())
79+
return nil
80+
}
81+
82+
// assumes that all subscriptions have already been created
83+
func loop(client *watchman.Client, subs []Subscription, root, rel string) {
84+
for {
85+
rawNotif, ok := <-client.Notifications()
86+
if !ok {
87+
log.Warn().Msg("watchman client shut down, exiting watch loop")
88+
return
89+
}
90+
91+
notif, ok := rawNotif.(*protocol.Subscription)
92+
if !ok {
93+
log.Debug().Msg("got a non-subscription notification, ignoring")
94+
}
95+
96+
if notif.IsFreshInstance() {
97+
log.Debug().Msg("fresh instance notification, ignoring")
98+
}
99+
100+
files := []string{}
101+
for _, rawFile := range notif.Files() {
102+
// if the query fields change, this will need to be a map[string]any
103+
file := rawFile.(string)
104+
files = append(files, file)
105+
}
106+
107+
for _, sub := range subs {
108+
sub.updates <- files
109+
}
110+
}
111+
}
112+
113+
func (w *Watchman) Subscribe(glob string) <-chan []string {
114+
ch := make(chan []string)
115+
w.subscriptions = append(w.subscriptions, Subscription{
116+
updates: ch,
117+
glob: glob,
118+
})
119+
120+
return ch
121+
}

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/InVisionApp/go-health/v2 v2.1.4
88
github.com/adrg/xdg v0.4.0
99
github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5
10+
github.com/cdmistman/watchman v0.2.1-0.20240521013409-16b1ae353832
1011
github.com/creack/pty v1.1.21
1112
github.com/f1bonacc1/glippy v0.0.0-20230614190937-e7ca07f99f6f
1213
github.com/fatih/color v1.16.0
@@ -31,9 +32,8 @@ replace github.com/cakturk/go-netstat => github.com/f1bonacc1/netstat v0.0.0-202
3132
require (
3233
github.com/InVisionApp/go-logger v1.0.1 // indirect
3334
github.com/KyleBanks/depth v1.2.1 // indirect
34-
github.com/Microsoft/go-winio v0.5.2 // indirect
35+
github.com/Microsoft/go-winio v0.6.2 // indirect
3536
github.com/bytedance/sonic v1.10.2 // indirect
36-
github.com/cdmistman/watchman v0.2.1-0.20240520005913-d8783be7f453 // indirect
3737
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
3838
github.com/chenzhuoyu/iasm v0.9.1 // indirect
3939
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
@@ -76,5 +76,5 @@ require (
7676
github.com/rs/zerolog v1.32.0
7777
github.com/swaggo/files v1.0.1
7878
github.com/swaggo/gin-swagger v1.6.0
79-
golang.org/x/sys v0.19.0 // indirect
79+
golang.org/x/sys v0.20.0 // indirect
8080
)

go.sum

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc
88
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
99
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
1010
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
11+
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
12+
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
1113
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
1214
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
1315
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
@@ -21,6 +23,8 @@ github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZF
2123
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
2224
github.com/cdmistman/watchman v0.2.1-0.20240520005913-d8783be7f453 h1:5kwNOesBL08pw4GurjnaLfXik1Vh7lq4aVZro61+/vM=
2325
github.com/cdmistman/watchman v0.2.1-0.20240520005913-d8783be7f453/go.mod h1:6Vf499nk4H8euH9INguY3dux8DWPmTkGnn1Un/yj+ek=
26+
github.com/cdmistman/watchman v0.2.1-0.20240521013409-16b1ae353832 h1:Ax/3QlycSW6UV2Gcl1cWB0xfvHej85JhZYPJuQBD1gs=
27+
github.com/cdmistman/watchman v0.2.1-0.20240521013409-16b1ae353832/go.mod h1:dWMrnZS+cSIn+wGnWneps2bPUWvwvFLKq2/iS4yJOa4=
2428
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
2529
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
2630
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
@@ -46,6 +50,8 @@ github.com/f1bonacc1/netstat v0.0.0-20230714090734-adb3fa07cab7 h1:CNJScrucGEDyO
4650
github.com/f1bonacc1/netstat v0.0.0-20230714090734-adb3fa07cab7/go.mod h1:jtAfVaU/2cu1+wdSRPWE2c1N2qeAA3K4RH9pYgqwets=
4751
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
4852
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
53+
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
54+
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
4955
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
5056
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
5157
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
@@ -104,7 +110,6 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
104110
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
105111
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
106112
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
107-
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
108113
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
109114
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
110115
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -151,8 +156,8 @@ github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
151156
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
152157
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
153158
github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
154-
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
155159
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
160+
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
156161
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
157162
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
158163
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
@@ -169,8 +174,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
169174
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
170175
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
171176
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
172-
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
173177
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
178+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
179+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
174180
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
175181
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
176182
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
@@ -227,6 +233,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
227233
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
228234
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
229235
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
236+
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
237+
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
230238
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
231239
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
232240
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=

src/app/process.go

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import (
55
"context"
66
"errors"
77
"fmt"
8-
"github.com/cakturk/go-netstat/netstat"
9-
"github.com/f1bonacc1/process-compose/src/types"
108
"io"
119
"math/rand"
1210
"os"
@@ -17,6 +15,9 @@ import (
1715
"syscall"
1816
"time"
1917

18+
"github.com/cakturk/go-netstat/netstat"
19+
"github.com/f1bonacc1/process-compose/src/types"
20+
2021
"github.com/f1bonacc1/process-compose/src/command"
2122
"github.com/f1bonacc1/process-compose/src/health"
2223
"github.com/f1bonacc1/process-compose/src/pclog"
@@ -65,6 +66,13 @@ type Process struct {
6566
isMain bool
6667
extraArgs []string
6768
isStopped atomic.Bool
69+
isDevRestart atomic.Bool
70+
devWatchers []devWatch
71+
}
72+
73+
type devWatch struct {
74+
sub *WatchmanSub
75+
config *types.Watch
6876
}
6977

7078
func NewProcess(
@@ -77,6 +85,7 @@ func NewProcess(
7785
printLogs bool,
7886
isMain bool,
7987
extraArgs []string,
88+
watchman *Watchman,
8089
) *Process {
8190
colNumeric := rand.Intn(int(color.FgHiWhite)-int(color.FgHiBlack)) + int(color.FgHiBlack)
8291

@@ -104,6 +113,7 @@ func NewProcess(
104113
proc.setUpProbes()
105114
proc.procCond = *sync.NewCond(proc)
106115
proc.procStartedCond = *sync.NewCond(proc)
116+
proc.setUpWatchman(watchman)
107117
return proc
108118
}
109119

@@ -139,9 +149,9 @@ func (p *Process) run() int {
139149

140150
p.startProbes()
141151

142-
//Wait should wait for I/O consumption, but if the execution is too fast
143-
//e.g. echo 'hello world' the output will not reach the pipe
144-
//TODO Fix this
152+
// Wait should wait for I/O consumption, but if the execution is too fast
153+
// e.g. echo 'hello world' the output will not reach the pipe
154+
// TODO Fix this
145155
time.Sleep(50 * time.Millisecond)
146156
_ = p.command.Wait()
147157
p.Lock()
@@ -212,7 +222,6 @@ func (p *Process) getCommander() command.Commander {
212222
p.mergeExtraArgs(),
213223
)
214224
}
215-
216225
}
217226

218227
func (p *Process) mergeExtraArgs() []string {
@@ -254,6 +263,11 @@ func (p *Process) isRestartable() bool {
254263
p.Lock()
255264
exitCode := p.getExitCode()
256265
p.Unlock()
266+
267+
if p.isDevRestart.Swap(false) {
268+
return true
269+
}
270+
257271
if p.isStopped.Swap(false) {
258272
return false
259273
}
@@ -381,16 +395,35 @@ func (p *Process) isRunning() bool {
381395

382396
func (p *Process) prepareForShutDown() {
383397
// prevent restart during global shutdown or scale down
384-
//p.procConf.RestartPolicy.Restart = types.RestartPolicyNo
398+
// p.procConf.RestartPolicy.Restart = types.RestartPolicyNo
385399
p.isStopped.Store(true)
386-
387400
}
388401

389402
func (p *Process) onProcessStart() {
390403
if isStringDefined(p.procConf.LogLocation) {
391404
p.logger.Open(p.getLogPath(), p.procConf.LoggerConfig)
392405
}
393406

407+
for _, watch := range p.devWatchers {
408+
go func() {
409+
for {
410+
files, ok := watch.sub.Recv()
411+
if !ok {
412+
log.
413+
Debug().
414+
Msg("watchman sub closed, exiting")
415+
return
416+
}
417+
418+
if len(files) == 0 {
419+
continue
420+
}
421+
422+
p.isDevRestart.Store(true)
423+
}
424+
}()
425+
}
426+
394427
p.Lock()
395428
p.started = true
396429
p.Unlock()
@@ -457,8 +490,8 @@ func (p *Process) updateProcState() {
457490
p.procState.Name = p.getName()
458491
}
459492
p.procState.IsRunning = isRunning
460-
461493
}
494+
462495
func (p *Process) setStartTime(startTime time.Time) {
463496
p.timeMutex.Lock()
464497
defer p.timeMutex.Unlock()
@@ -663,6 +696,13 @@ func (p *Process) onReadinessCheckEnd(isOk, isFatal bool, err string) {
663696
}
664697
}
665698

699+
func (p *Process) setUpWatchman(watchman *Watchman) {
700+
for _, config := range p.procConf.Watch {
701+
recv := watchman.Subscribe(config)
702+
p.devWatchers = append(p.devWatchers, devWatch{recv, config})
703+
}
704+
}
705+
666706
func (p *Process) validateProcess() error {
667707
if isStringDefined(p.procConf.WorkingDir) {
668708
stat, err := os.Stat(p.procConf.WorkingDir)

0 commit comments

Comments
 (0)