Skip to content

Commit 6ba68ca

Browse files
authored
chore: make fiximports run in parallel (#12985)
1 parent 13d5007 commit 6ba68ca

File tree

1 file changed

+104
-28
lines changed

1 file changed

+104
-28
lines changed

scripts/fiximports/main.go

Lines changed: 104 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ package main
33
import (
44
"bytes"
55
"fmt"
6-
"io"
76
"io/fs"
87
"os"
98
"path/filepath"
109
"regexp"
10+
"runtime"
1111
"strings"
1212

13+
"golang.org/x/sync/errgroup"
1314
"golang.org/x/tools/imports"
15+
"golang.org/x/xerrors"
1416
)
1517

1618
var (
@@ -25,7 +27,18 @@ var (
2527
consecutiveNewlinesRegex = regexp.MustCompile(`\n\s*\n`)
2628
)
2729

30+
type fileContent struct {
31+
path string
32+
original []byte
33+
current []byte
34+
changed bool
35+
}
36+
2837
func main() {
38+
numWorkers := runtime.NumCPU()
39+
40+
// Collect all the filenames that we want to process
41+
var files []string
2942
if err := filepath.Walk(".", func(path string, info fs.FileInfo, err error) error {
3043
switch {
3144
case err != nil:
@@ -39,49 +52,112 @@ func main() {
3952
!strings.HasSuffix(info.Name(), ".go"):
4053
return nil
4154
}
42-
return fixGoImports(path)
55+
files = append(files, path)
56+
return nil
4357
}); err != nil {
44-
fmt.Printf("Error fixing go imports: %v\n", err)
58+
_, _ = fmt.Fprintf(os.Stderr, "Error walking directory: %v\n", err)
4559
os.Exit(1)
4660
}
47-
}
4861

49-
func fixGoImports(path string) error {
50-
sourceFile, err := os.OpenFile(path, os.O_RDWR, 0666)
62+
// Read all file contents in parallel
63+
fileContents, err := readFilesParallel(files, numWorkers)
5164
if err != nil {
52-
return err
65+
_, _ = fmt.Fprintf(os.Stderr, "Error reading files: %v\n", err)
66+
os.Exit(1)
5367
}
54-
defer func() { _ = sourceFile.Close() }()
5568

56-
source, err := io.ReadAll(sourceFile)
57-
if err != nil {
58-
return err
59-
}
60-
formatted := collapseImportNewlines(source)
69+
// Because we have multiple ways of separating imports, we have to imports.Process for each one
70+
// but imports.LocalPrefix is a global, so we have to set it for each group and process files
71+
// in parallel.
6172
for _, prefix := range groupByPrefixes {
6273
imports.LocalPrefix = prefix
63-
formatted, err = imports.Process(path, formatted, nil)
64-
if err != nil {
65-
return err
74+
if err := processFilesParallel(fileContents, numWorkers); err != nil {
75+
_, _ = fmt.Fprintf(os.Stderr, "Error processing files with prefix %s: %v\n", prefix, err)
76+
os.Exit(1)
6677
}
6778
}
68-
if !bytes.Equal(source, formatted) {
69-
if err := replaceFileContent(sourceFile, formatted); err != nil {
70-
return err
71-
}
79+
80+
// Write modified files in parallel
81+
if err := writeFilesParallel(fileContents, numWorkers); err != nil {
82+
_, _ = fmt.Fprintf(os.Stderr, "Error writing files: %v\n", err)
83+
os.Exit(1)
7284
}
73-
return nil
7485
}
7586

76-
func replaceFileContent(target *os.File, replacement []byte) error {
77-
if _, err := target.Seek(0, io.SeekStart); err != nil {
78-
return err
87+
func readFilesParallel(files []string, numWorkers int) ([]*fileContent, error) {
88+
fileContents := make([]*fileContent, len(files))
89+
90+
var g errgroup.Group
91+
g.SetLimit(numWorkers)
92+
93+
for i, path := range files {
94+
g.Go(func() error {
95+
content, err := os.ReadFile(path)
96+
if err != nil {
97+
return xerrors.Errorf("reading %s: %w", path, err)
98+
}
99+
100+
// Collapse is a cheap operation to do here
101+
collapsed := collapseImportNewlines(content)
102+
fileContents[i] = &fileContent{
103+
path: path,
104+
original: content,
105+
current: collapsed,
106+
changed: !bytes.Equal(content, collapsed),
107+
}
108+
return nil
109+
})
79110
}
80-
written, err := target.Write(replacement)
81-
if err != nil {
82-
return err
111+
112+
if err := g.Wait(); err != nil {
113+
return nil, err
114+
}
115+
116+
return fileContents, nil
117+
}
118+
119+
func processFilesParallel(fileContents []*fileContent, numWorkers int) error {
120+
var g errgroup.Group
121+
g.SetLimit(numWorkers)
122+
123+
for _, file := range fileContents {
124+
if file == nil {
125+
continue
126+
}
127+
g.Go(func() error {
128+
formatted, err := imports.Process(file.path, file.current, nil)
129+
if err != nil {
130+
return xerrors.Errorf("processing %s: %w", file.path, err)
131+
}
132+
133+
if !bytes.Equal(file.current, formatted) {
134+
file.current = formatted
135+
file.changed = true
136+
}
137+
return nil
138+
})
83139
}
84-
return target.Truncate(int64(written))
140+
141+
return g.Wait()
142+
}
143+
144+
func writeFilesParallel(fileContents []*fileContent, numWorkers int) error {
145+
var g errgroup.Group
146+
g.SetLimit(numWorkers)
147+
148+
for _, file := range fileContents {
149+
if file == nil || !file.changed {
150+
continue
151+
}
152+
g.Go(func() error {
153+
if err := os.WriteFile(file.path, file.current, 0666); err != nil {
154+
return xerrors.Errorf("writing %s: %w", file.path, err)
155+
}
156+
return nil
157+
})
158+
}
159+
160+
return g.Wait()
85161
}
86162

87163
func collapseImportNewlines(content []byte) []byte {

0 commit comments

Comments
 (0)