Skip to content

Commit 389905f

Browse files
authored
Merge pull request #549 from dlorenc/mtime
Add an mtime file watcher.
2 parents 4c6664b + f1f4ae5 commit 389905f

File tree

2 files changed

+132
-57
lines changed

2 files changed

+132
-57
lines changed

pkg/skaffold/watch/watch.go

+88-21
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"fmt"
2222
"io"
23+
"os"
2324
"path/filepath"
2425
"sort"
2526
"time"
@@ -48,36 +49,102 @@ type fsWatcher struct {
4849
files map[string]bool
4950
}
5051

52+
type mtimeWatcher struct {
53+
files map[string]time.Time
54+
}
55+
56+
func (m *mtimeWatcher) Start(ctx context.Context, out io.Writer, onChange func([]string) error) error {
57+
58+
c := time.NewTicker(2 * time.Second)
59+
60+
changedPaths := map[string]bool{}
61+
62+
fmt.Fprintln(out, "Watching for changes...")
63+
for {
64+
select {
65+
case <-c.C:
66+
// add things to changedpaths
67+
for f := range m.files {
68+
fi, err := os.Stat(f)
69+
if err != nil {
70+
return errors.Wrapf(err, "statting file %s", f)
71+
}
72+
mtime, ok := m.files[f]
73+
if !ok {
74+
logrus.Warningf("file %s not found.", f)
75+
continue
76+
}
77+
if mtime != fi.ModTime() {
78+
m.files[f] = fi.ModTime()
79+
changedPaths[f] = true
80+
}
81+
}
82+
if len(changedPaths) > 0 {
83+
if err := onChange(sortedPaths(changedPaths)); err != nil {
84+
return errors.Wrap(err, "change callback")
85+
}
86+
logrus.Debugf("Files changed: %v", changedPaths)
87+
changedPaths = map[string]bool{}
88+
}
89+
case <-ctx.Done():
90+
return nil
91+
}
92+
}
93+
}
94+
5195
// NewWatcher creates a new Watcher on a list of files.
5296
func NewWatcher(paths []string) (Watcher, error) {
53-
w, err := fsnotify.NewWatcher()
54-
if err != nil {
55-
return nil, errors.Wrapf(err, "creating watcher")
97+
sort.Strings(paths)
98+
99+
// Get the watcher type to use, defaulting to mtime.
100+
watcher := os.Getenv("SKAFFOLD_FILE_WATCHER")
101+
if watcher == "" {
102+
watcher = "mtime"
56103
}
57104

58-
files := map[string]bool{}
105+
switch watcher {
106+
case "mtime":
107+
logrus.Info("Starting mtime file watcher.")
108+
files := map[string]time.Time{}
109+
for _, p := range paths {
110+
fi, err := os.Stat(p)
111+
if err != nil {
112+
return nil, err
113+
}
114+
files[p] = fi.ModTime()
115+
}
116+
return &mtimeWatcher{
117+
files: files,
118+
}, nil
119+
case "fsnotify":
120+
logrus.Info("Starting fsnotify file watcher.")
121+
w, err := fsnotify.NewWatcher()
122+
if err != nil {
123+
return nil, errors.Wrapf(err, "creating watcher")
124+
}
59125

60-
sort.Strings(paths)
61-
for _, p := range paths {
62-
files[p] = true
63-
logrus.Infof("Added watch for %s", p)
126+
files := map[string]bool{}
64127

65-
if err := w.Add(p); err != nil {
66-
w.Close()
67-
return nil, errors.Wrapf(err, "adding watch for %s", p)
68-
}
128+
for _, p := range paths {
129+
files[p] = true
130+
logrus.Debugf("Added watch for %s", p)
131+
132+
if err := w.Add(p); err != nil {
133+
w.Close()
134+
return nil, errors.Wrapf(err, "adding watch for %s", p)
135+
}
69136

70-
if err := w.Add(filepath.Dir(p)); err != nil {
71-
w.Close()
72-
return nil, errors.Wrapf(err, "adding watch for %s", p)
137+
if err := w.Add(filepath.Dir(p)); err != nil {
138+
w.Close()
139+
return nil, errors.Wrapf(err, "adding watch for %s", p)
140+
}
73141
}
142+
return &fsWatcher{
143+
watcher: w,
144+
files: files,
145+
}, nil
74146
}
75-
76-
logrus.Info("Watch is ready")
77-
return &fsWatcher{
78-
watcher: w,
79-
files: files,
80-
}, nil
147+
return nil, fmt.Errorf("unknown watch type: %s", watcher)
81148
}
82149

83150
// Start watches a set of files for changes, and calls `onChange`

pkg/skaffold/watch/watch_test.go

+44-36
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ package watch
1717

1818
import (
1919
"context"
20+
"fmt"
2021
"io/ioutil"
22+
"os"
2123
"path/filepath"
2224
"testing"
2325

@@ -26,6 +28,7 @@ import (
2628
)
2729

2830
func TestWatch(t *testing.T) {
31+
watchers := []string{"mtime", "fsnotify"}
2932
var tests = []struct {
3033
description string
3134
createFiles []string
@@ -57,50 +60,55 @@ func TestWatch(t *testing.T) {
5760
},
5861
}
5962

60-
for _, test := range tests {
61-
t.Run(test.description, func(t *testing.T) {
62-
tmp, teardown := testutil.TempDir(t)
63-
defer teardown()
64-
65-
for _, p := range prependParentDir(tmp, test.createFiles) {
66-
write(t, p, "")
67-
}
68-
69-
watcher, err := NewWatcher(prependParentDir(tmp, test.watchFiles))
70-
if err == nil && test.shouldErr {
71-
t.Errorf("Expected error, but returned none")
72-
return
73-
}
74-
if err != nil && !test.shouldErr {
75-
t.Errorf("Unexpected error: %s", err)
76-
return
77-
}
78-
if err != nil && test.shouldErr {
79-
return
80-
}
81-
82-
for _, p := range prependParentDir(tmp, test.writes) {
83-
write(t, p, "CONTENT")
84-
}
85-
86-
ctx, cancel := context.WithCancel(context.Background())
87-
watcher.Start(ctx, ioutil.Discard, func(actual []string) error {
88-
defer cancel()
63+
for _, watcher := range watchers {
64+
for _, test := range tests {
65+
t.Run(fmt.Sprintf("%s %s", test.description, watcher), func(t *testing.T) {
66+
os.Setenv("SKAFFOLD_FILE_WATCHER", watcher)
67+
defer os.Setenv("SKAFFOLD_FILE_WATCHER", "")
68+
tmp, teardown := testutil.TempDir(t)
69+
defer teardown()
8970

90-
expected := prependParentDir(tmp, test.expectedChanges)
71+
for _, p := range prependParentDir(tmp, test.createFiles) {
72+
write(t, p, "")
73+
}
9174

92-
if diff := cmp.Diff(expected, actual); diff != "" {
93-
t.Errorf("Expected %+v, Actual %+v", expected, actual)
75+
watcher, err := NewWatcher(prependParentDir(tmp, test.watchFiles))
76+
if err == nil && test.shouldErr {
77+
t.Errorf("Expected error, but returned none")
78+
return
79+
}
80+
if err != nil && !test.shouldErr {
81+
t.Errorf("Unexpected error: %s", err)
82+
return
9483
}
84+
if err != nil && test.shouldErr {
85+
return
86+
}
87+
88+
ctx, cancel := context.WithCancel(context.Background())
9589

96-
return nil
90+
defer cancel()
91+
go watcher.Start(ctx, ioutil.Discard, func(actual []string) error {
92+
93+
expected := prependParentDir(tmp, test.expectedChanges)
94+
95+
if diff := cmp.Diff(expected, actual); diff != "" {
96+
t.Errorf("Expected %+v, Actual %+v", expected, actual)
97+
}
98+
99+
return nil
100+
})
101+
102+
for _, p := range prependParentDir(tmp, test.writes) {
103+
write(t, p, "CONTENT")
104+
}
97105
})
98-
})
106+
}
99107
}
100108
}
101109
func write(t *testing.T, path string, content string) {
102-
if err := ioutil.WriteFile(path, []byte(content), 0640); err != nil {
103-
t.Errorf("writing mock fs file: %s", err)
110+
if err := ioutil.WriteFile(path, []byte(content), 0644); err != nil {
111+
t.Fatalf("writing file: %s", err)
104112
}
105113
}
106114

0 commit comments

Comments
 (0)