Skip to content

Implement smart clean#4413

Open
emcfarlane wants to merge 4 commits intomainfrom
ed/smartClean
Open

Implement smart clean#4413
emcfarlane wants to merge 4 commits intomainfrom
ed/smartClean

Conversation

@emcfarlane
Copy link
Contributor

This implements smart clean for buf generate --clean as described in this comment, building on the mtime-preserving skip-unchanged behavior from #4396.

With --clean, buf generate now handles each output directory intelligently:

  • Unchanged files are skipped (mtime preserved)
  • Changed files are overwritten
  • New files are created
  • Stale files that are no longer generated are deleted
  • Empty directories left behind are cleaned up

This replaces the previous delete-everything-then-regenerate approach with a compare-and-reconcile model, which is safer for mtime-based build systems (make, cargo, ninja) and avoids unnecessary downstream rebuilds.

Architecture change: the ResponseWriter is now created once per Generate() call rather than per image. This is required so the writer can see the full set of generated paths across all images before deciding what to delete. A side effect is improved atomicity: if any plugin fails, no partial output is written. The trade-off is holding all images' output in
memory until flush, which should be negligible in practice.

Fixes #3126

@github-actions
Copy link
Contributor

github-actions bot commented Mar 24, 2026

The latest Buf updates on your PR. Results from workflow Buf CI / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedMar 25, 2026, 7:15 PM

Copy link
Member

@stefanvanburen stefanvanburen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking good; ideally I'd love to use something like this to clean up Makefile targets like this one where we are doing multiple generations and can't use clean because of the repeat buf generate calls. (probably still can't update that one because of the make variables, but this will still be great.)

Just a couple suggestions on my end.

if err == nil && bytes.Equal(existingContent, newContent) {
return nil
}
return os.WriteFile(outFilePath, newContent, 0600)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just checking: is this matching the behavior of os.Create? just wondering about the 0600

Copy link
Contributor Author

@emcfarlane emcfarlane Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, 0666.

// isArchivePath returns true if the given path has a .zip or .jar extension.
func isArchivePath(path string) bool {
ext := filepath.Ext(path)
return ext == ".zip" || ext == ".jar"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like we should consolidate these extensions into some consts/seqs so we don't miss updating them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made consts in this package. It is used outside, but rarely.

outDirPath string,
retainPaths map[string]struct{},
) error {
ctx := context.Background()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should thread through the ctx, even if Close doesn't take one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed interface to Close(ctx context.Context) error

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fwiw, I'd be fine with leaving them as io.Closers and just having a single context.Background() that gets passed down within the Closer itself. But the update you made also makes sense to me. Just figured we ought to share one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make buf generate --clean a per-plugin option that does a "smart" clean

2 participants