From 916bfbfa2b150c92dc9ae282f5e32c07e6b6152d Mon Sep 17 00:00:00 2001 From: "Nathan J. Mehl" Date: Tue, 3 Jun 2025 18:05:30 -0400 Subject: [PATCH] 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). --- internal/cmd/cmd.go | 49 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 93fd6bbeaa..2a64ce516d 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -204,6 +204,15 @@ var genCmd = &cobra.Command{ } defer trace.StartRegion(cmd.Context(), "writefiles").End() for filename, source := range output { + different, err := diffFile(filename, []byte(source)) + if err != nil { + fmt.Fprintf(stderr, "%s: %s\n", filename, err) + return err + } + if !different { + // if the file is the same, we can skip writing it + continue + } os.MkdirAll(filepath.Dir(filename), 0755) if err := os.WriteFile(filename, []byte(source), 0644); err != nil { fmt.Fprintf(stderr, "%s: %s\n", filename, err) @@ -278,3 +287,43 @@ var diffCmd = &cobra.Command{ return nil }, } + +// diffFile is a helper function that compares the contents of a named file to the +// "source" byte array. It returns true if the contents are different, and false if they are the same. +// If the named file does not exist, it returns true. It checks in 100 kilobyte chunks to avoid +// loading the entire file into memory at once. +func diffFile(name string, source []byte) (bool, error) { + fileInfo, err := os.Stat(name) + if err != nil { + if os.IsNotExist(err) { + return true, nil // file does not exist, which is pretty different + } + return false, fmt.Errorf("error opening file %s: %w", name, err) + } + targetFileSize := fileInfo.Size() + if targetFileSize != int64(len(source)) { + return true, nil // sizes are different, so contents are different + } + + f, err := os.Open(name) + defer f.Close() + if err != nil { + return false, fmt.Errorf("error opening file %s: %w", name, err) + } + + buf := make([]byte, 100*1024) // 100 kilobytes + for { + n, err := f.Read(buf) + if n > 0 && !bytes.Equal(buf[:n], source) { + return true, nil // contents are different + } + if err == io.EOF { + break // end of file reached + } + if err != nil { + return false, fmt.Errorf("error reading file %s: %w", name, err) + } + } + + return false, nil // contents are the same +}