diff --git a/internal/librariangen/config/repoconfig.go b/internal/librariangen/config/repoconfig.go index e6700d802211..b2a969342522 100644 --- a/internal/librariangen/config/repoconfig.go +++ b/internal/librariangen/config/repoconfig.go @@ -48,6 +48,11 @@ type ModuleConfig struct { ModulePathVersion string `yaml:"module_path_version"` // APIs is the list of APIs within this module (that need overrides). APIs []*APIConfig `yaml:"apis"` + // DeleteGenerationOutputPaths specifies paths (files or directories) to + // be deleted from the output directory at the end of generation. This is for files + // which it is difficult to prevent from being generated, but which shouldn't appear + // in the repo. + DeleteGenerationOutputPaths []string `yaml:"delete_generation_output_paths"` } // APIConfig provides per-API configuration to override defaults, diff --git a/internal/librariangen/generate/generator.go b/internal/librariangen/generate/generator.go index 2893438b409c..30b6ce9a406d 100644 --- a/internal/librariangen/generate/generator.go +++ b/internal/librariangen/generate/generator.go @@ -132,6 +132,9 @@ func Generate(ctx context.Context, cfg *Config) error { return fmt.Errorf("librariangen: post-processing failed: %w", err) } } + if err := deleteOutputPaths(cfg.OutputDir, moduleConfig.DeleteGenerationOutputPaths); err != nil { + return fmt.Errorf("librariangen: failed to delete paths specified in delete_generation_output_paths: %w", err) + } slog.Debug("librariangen: generate command finished") return nil @@ -280,3 +283,20 @@ func moveFiles(sourceDir, targetDir string) error { } return nil } + +// deleteOutputPaths deletes the specified paths, which may be files +// or directories, relative to the output directory. This is an emergency +// escape hatch for situations where files are generated that we don't want +// to include, such as the internal/generated/snippets/storage/internal directory. +// This is configured in repo-config.yaml at the library level, with the key +// delete_generation_output_paths. +func deleteOutputPaths(outputDir string, pathsToDelete []string) error { + for _, path := range pathsToDelete { + // This is so rare that it's useful to be able to validate it easily. + slog.Info("deleting output path", "path", path) + if err := os.RemoveAll(filepath.Join(outputDir, path)); err != nil { + return err + } + } + return nil +} diff --git a/internal/librariangen/generate/generator_test.go b/internal/librariangen/generate/generator_test.go index a8256509e5cc..4f087d48a50d 100644 --- a/internal/librariangen/generate/generator_test.go +++ b/internal/librariangen/generate/generator_test.go @@ -468,17 +468,50 @@ func TestApplyModuleVersion(t *testing.T) { if tt.wantErr { return } - - for _, wantPresent := range tt.wantPresent { - if _, err := os.Stat(filepath.Join(tmpDir, wantPresent)); err != nil { - t.Errorf("wanted present, was absent: %s", wantPresent) - } - } - for _, wantAbsent := range tt.wantAbsent { - if _, err := os.Stat(filepath.Join(tmpDir, wantAbsent)); !os.IsNotExist(err) { - t.Errorf("wanted absent, was missing: %s", wantAbsent) - } - } + assertPresent(t, tmpDir, tt.wantPresent) + assertAbsent(t, tmpDir, tt.wantAbsent) }) } } + +func TestDeleteOutputPaths(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "apply-module-version-test") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + // We create file1, file2 and dir1/file3, then delete + // file1 and dir1; only file2 should be left. + if err := os.MkdirAll(filepath.Join(tmpDir, "dir1"), 0755); err != nil { + t.Fatalf("failed to create dir1: %v", err) + } + if err := os.WriteFile(filepath.Join(tmpDir, "file1"), []byte("test"), 0644); err != nil { + t.Fatalf("failed to write file1: %v", err) + } + if err := os.WriteFile(filepath.Join(tmpDir, "file2"), []byte("test"), 0644); err != nil { + t.Fatalf("failed to write file file2: %v", err) + } + if err := os.WriteFile(filepath.Join(tmpDir, "dir1", "file3"), []byte("test"), 0644); err != nil { + t.Fatalf("failed to write file dir1/file3: %v", err) + } + deleteOutputPaths(tmpDir, []string{"file1", "dir1"}) + assertPresent(t, tmpDir, []string{"file2"}) + assertAbsent(t, tmpDir, []string{"file1", "dir1"}) +} + +func assertPresent(t *testing.T, dir string, paths []string) { + t.Helper() + for _, path := range paths { + if _, err := os.Stat(filepath.Join(dir, path)); err != nil { + t.Errorf("wanted present, stat failed: %s", path) + } + } +} + +func assertAbsent(t *testing.T, dir string, paths []string) { + t.Helper() + for _, path := range paths { + if _, err := os.Stat(filepath.Join(dir, path)); !os.IsNotExist(err) { + t.Errorf("wanted absent, was present (or stat failed): %s", path) + } + } +}