Skip to content

Commit d80a685

Browse files
authored
feat(internal/rust): clean output directory before generation (#3102)
Remove generated files before regenerating, preserving only the files in Library.Keep. This ensures stale files from previous generations don't persist.
1 parent b063e4b commit d80a685

File tree

2 files changed

+125
-0
lines changed

2 files changed

+125
-0
lines changed

internal/librarian/generate.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ import (
1818
"context"
1919
"errors"
2020
"fmt"
21+
"io/fs"
22+
"os"
23+
"path/filepath"
2124

2225
"github.com/googleapis/librarian/internal/config"
2326
"github.com/googleapis/librarian/internal/fetch"
@@ -111,6 +114,10 @@ func generate(ctx context.Context, language string, library *config.Library, sou
111114
case "testhelper":
112115
err = testGenerate(library)
113116
case "rust":
117+
keep := append(library.Keep, "Cargo.toml")
118+
if err := cleanOutput(library.Output, keep); err != nil {
119+
return err
120+
}
114121
err = rust.Generate(ctx, library, sources)
115122
default:
116123
err = fmt.Errorf("generate not implemented for %q", language)
@@ -132,3 +139,33 @@ func fetchGoogleapisDir(ctx context.Context, sources *config.Sources) (string, e
132139
}
133140
return fetch.RepoDir(ctx, googleapisRepo, sources.Googleapis.Commit, sources.Googleapis.SHA256)
134141
}
142+
143+
// cleanOutput removes all files in dir except those in keep. The keep list
144+
// should contain paths relative to dir. It returns an error if any file in
145+
// keep does not exist.
146+
func cleanOutput(dir string, keep []string) error {
147+
keepSet := make(map[string]bool)
148+
for _, k := range keep {
149+
path := filepath.Join(dir, k)
150+
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
151+
return fmt.Errorf("%s: file %q in keep list does not exist", dir, k)
152+
}
153+
keepSet[k] = true
154+
}
155+
return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
156+
if err != nil {
157+
return err
158+
}
159+
if d.IsDir() {
160+
return nil
161+
}
162+
rel, err := filepath.Rel(dir, path)
163+
if err != nil {
164+
return err
165+
}
166+
if keepSet[rel] {
167+
return nil
168+
}
169+
return os.Remove(path)
170+
})
171+
}

internal/librarian/generate_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"fmt"
2020
"os"
2121
"path/filepath"
22+
"slices"
2223
"testing"
2324

2425
"github.com/google/go-cmp/cmp"
@@ -137,3 +138,90 @@ libraries:
137138
})
138139
}
139140
}
141+
142+
func TestCleanOutput(t *testing.T) {
143+
for _, test := range []struct {
144+
name string
145+
files []string
146+
keep []string
147+
want []string
148+
wantErr bool
149+
}{
150+
{
151+
name: "removes all except keep list",
152+
files: []string{"Cargo.toml", "README.md", "src/lib.rs"},
153+
keep: []string{"Cargo.toml"},
154+
want: []string{"Cargo.toml"},
155+
},
156+
{
157+
name: "empty directory with keep list",
158+
files: []string{},
159+
keep: []string{"Cargo.toml"},
160+
wantErr: true,
161+
},
162+
{
163+
name: "only kept file",
164+
files: []string{"Cargo.toml"},
165+
keep: []string{"Cargo.toml"},
166+
want: []string{"Cargo.toml"},
167+
},
168+
{
169+
name: "keep file not found",
170+
files: []string{"README.md", "src/lib.rs"},
171+
keep: []string{"Cargo.toml"},
172+
wantErr: true,
173+
},
174+
{
175+
name: "keep multiple files",
176+
files: []string{"Cargo.toml", "README.md", "src/lib.rs"},
177+
keep: []string{"Cargo.toml", "README.md"},
178+
want: []string{"Cargo.toml", "README.md"},
179+
},
180+
{
181+
name: "empty keep list",
182+
files: []string{"Cargo.toml", "README.md"},
183+
keep: []string{},
184+
want: []string{},
185+
},
186+
{
187+
name: "keep nested files",
188+
files: []string{"Cargo.toml", "README.md", "src/lib.rs", "src/operation.rs", "src/endpoint.rs"},
189+
keep: []string{"src/operation.rs", "src/endpoint.rs"},
190+
want: []string{"src/endpoint.rs", "src/operation.rs"},
191+
},
192+
} {
193+
t.Run(test.name, func(t *testing.T) {
194+
dir := t.TempDir()
195+
for _, f := range test.files {
196+
path := filepath.Join(dir, f)
197+
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
198+
t.Fatal(err)
199+
}
200+
if err := os.WriteFile(path, []byte("test"), 0644); err != nil {
201+
t.Fatal(err)
202+
}
203+
}
204+
err := cleanOutput(dir, test.keep)
205+
if test.wantErr {
206+
if err == nil {
207+
t.Fatal("expected error, got nil")
208+
}
209+
return
210+
}
211+
if err != nil {
212+
t.Fatal(err)
213+
}
214+
var got []string
215+
for _, f := range test.files {
216+
if _, err := os.Stat(filepath.Join(dir, f)); err == nil {
217+
got = append(got, f)
218+
}
219+
}
220+
slices.Sort(got)
221+
slices.Sort(test.want)
222+
if !slices.Equal(got, test.want) {
223+
t.Errorf("got %v, want %v", got, test.want)
224+
}
225+
})
226+
}
227+
}

0 commit comments

Comments
 (0)