Skip to content

Commit 916bfbf

Browse files
committed
Avoid updating target file mtime in 'generate'
If the target file exists on disk already, compare its contents to the generated source in memory: only update the target file on disk if the contents differ or if the target file is missing. This should allow build tools downstream of sqlc to make conditional decisions based on whether the generated code files have been updated (e.g. mockgen).
1 parent a60f370 commit 916bfbf

File tree

1 file changed

+49
-0
lines changed

1 file changed

+49
-0
lines changed

internal/cmd/cmd.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,15 @@ var genCmd = &cobra.Command{
204204
}
205205
defer trace.StartRegion(cmd.Context(), "writefiles").End()
206206
for filename, source := range output {
207+
different, err := diffFile(filename, []byte(source))
208+
if err != nil {
209+
fmt.Fprintf(stderr, "%s: %s\n", filename, err)
210+
return err
211+
}
212+
if !different {
213+
// if the file is the same, we can skip writing it
214+
continue
215+
}
207216
os.MkdirAll(filepath.Dir(filename), 0755)
208217
if err := os.WriteFile(filename, []byte(source), 0644); err != nil {
209218
fmt.Fprintf(stderr, "%s: %s\n", filename, err)
@@ -278,3 +287,43 @@ var diffCmd = &cobra.Command{
278287
return nil
279288
},
280289
}
290+
291+
// diffFile is a helper function that compares the contents of a named file to the
292+
// "source" byte array. It returns true if the contents are different, and false if they are the same.
293+
// If the named file does not exist, it returns true. It checks in 100 kilobyte chunks to avoid
294+
// loading the entire file into memory at once.
295+
func diffFile(name string, source []byte) (bool, error) {
296+
fileInfo, err := os.Stat(name)
297+
if err != nil {
298+
if os.IsNotExist(err) {
299+
return true, nil // file does not exist, which is pretty different
300+
}
301+
return false, fmt.Errorf("error opening file %s: %w", name, err)
302+
}
303+
targetFileSize := fileInfo.Size()
304+
if targetFileSize != int64(len(source)) {
305+
return true, nil // sizes are different, so contents are different
306+
}
307+
308+
f, err := os.Open(name)
309+
defer f.Close()
310+
if err != nil {
311+
return false, fmt.Errorf("error opening file %s: %w", name, err)
312+
}
313+
314+
buf := make([]byte, 100*1024) // 100 kilobytes
315+
for {
316+
n, err := f.Read(buf)
317+
if n > 0 && !bytes.Equal(buf[:n], source) {
318+
return true, nil // contents are different
319+
}
320+
if err == io.EOF {
321+
break // end of file reached
322+
}
323+
if err != nil {
324+
return false, fmt.Errorf("error reading file %s: %w", name, err)
325+
}
326+
}
327+
328+
return false, nil // contents are the same
329+
}

0 commit comments

Comments
 (0)