Skip to content

Commit 750a846

Browse files
authored
A better go code formatter, and now make fmt can run in Windows (#17684)
* go build / format tools * re-format imports
1 parent 29cc169 commit 750a846

File tree

106 files changed

+731
-42
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+731
-42
lines changed

Makefile

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,6 @@ else
5858
SED_INPLACE := sed -i ''
5959
endif
6060

61-
GOFMT ?= gofmt -s
62-
6361
EXTRA_GOFLAGS ?=
6462

6563
MAKE_VERSION := $(shell $(MAKE) -v | head -n 1)
@@ -127,8 +125,6 @@ ifeq ($(filter $(TAGS_SPLIT),bindata),bindata)
127125
GO_SOURCES += $(BINDATA_DEST)
128126
endif
129127

130-
GO_SOURCES_OWN := $(filter-out vendor/% %/bindata.go, $(GO_SOURCES))
131-
132128
#To update swagger use: GO111MODULE=on go get -u github.com/go-swagger/go-swagger/cmd/swagger
133129
SWAGGER := $(GO) run -mod=vendor github.com/go-swagger/go-swagger/cmd/swagger
134130
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
@@ -238,7 +234,7 @@ clean:
238234
.PHONY: fmt
239235
fmt:
240236
@echo "Running go fmt..."
241-
@$(GOFMT) -w $(GO_SOURCES_OWN)
237+
@$(GO) run build/code-batch-process.go gitea-fmt -s -w '{file-list}'
242238

243239
.PHONY: vet
244240
vet:
@@ -298,20 +294,20 @@ misspell-check:
298294
$(GO) install github.com/client9/misspell/cmd/[email protected]; \
299295
fi
300296
@echo "Running misspell-check..."
301-
@misspell -error -i unknwon $(GO_SOURCES_OWN)
297+
@$(GO) run build/code-batch-process.go misspell -error -i unknwon '{file-list}'
302298

303299
.PHONY: misspell
304300
misspell:
305301
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
306302
$(GO) install github.com/client9/misspell/cmd/[email protected]; \
307303
fi
308304
@echo "Running go misspell..."
309-
@misspell -w -i unknwon $(GO_SOURCES_OWN)
305+
@$(GO) run build/code-batch-process.go misspell -w -i unknwon '{file-list}'
310306

311307
.PHONY: fmt-check
312308
fmt-check:
313309
# get all go files and run go fmt on them
314-
@diff=$$($(GOFMT) -d $(GO_SOURCES_OWN)); \
310+
@diff=$$($(GO) run build/code-batch-process.go gitea-fmt -s -d '{file-list}'); \
315311
if [ -n "$$diff" ]; then \
316312
echo "Please run 'make fmt' and commit the result:"; \
317313
echo "$${diff}"; \

build/code-batch-process.go

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build ignore
6+
// +build ignore
7+
8+
package main
9+
10+
import (
11+
"fmt"
12+
"log"
13+
"os"
14+
"os/exec"
15+
"path/filepath"
16+
"regexp"
17+
"strconv"
18+
"strings"
19+
20+
"code.gitea.io/gitea/build/codeformat"
21+
)
22+
23+
// Windows has a limitation for command line arguments, the size can not exceed 32KB.
24+
// So we have to feed the files to some tools (like gofmt/misspell`) batch by batch
25+
26+
// We also introduce a `gitea-fmt` command, it does better import formatting than gofmt/goimports
27+
28+
var optionLogVerbose bool
29+
30+
func logVerbose(msg string, args ...interface{}) {
31+
if optionLogVerbose {
32+
log.Printf(msg, args...)
33+
}
34+
}
35+
36+
func passThroughCmd(cmd string, args []string) error {
37+
foundCmd, err := exec.LookPath(cmd)
38+
if err != nil {
39+
log.Fatalf("can not find cmd: %s", cmd)
40+
}
41+
c := exec.Cmd{
42+
Path: foundCmd,
43+
Args: args,
44+
Stdin: os.Stdin,
45+
Stdout: os.Stdout,
46+
Stderr: os.Stderr,
47+
}
48+
return c.Run()
49+
}
50+
51+
type fileCollector struct {
52+
dirs []string
53+
includePatterns []*regexp.Regexp
54+
excludePatterns []*regexp.Regexp
55+
batchSize int
56+
}
57+
58+
func newFileCollector(fileFilter string, batchSize int) (*fileCollector, error) {
59+
co := &fileCollector{batchSize: batchSize}
60+
if fileFilter == "go-own" {
61+
co.dirs = []string{
62+
"build",
63+
"cmd",
64+
"contrib",
65+
"integrations",
66+
"models",
67+
"modules",
68+
"routers",
69+
"services",
70+
"tools",
71+
}
72+
co.includePatterns = append(co.includePatterns, regexp.MustCompile(`.*\.go$`))
73+
74+
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`.*\bbindata\.go$`))
75+
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`integrations/gitea-repositories-meta`))
76+
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`integrations/migration-test`))
77+
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`modules/git/tests`))
78+
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/fixtures`))
79+
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/migrations/fixtures`))
80+
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`services/gitdiff/testdata`))
81+
}
82+
83+
if co.dirs == nil {
84+
return nil, fmt.Errorf("unknown file-filter: %s", fileFilter)
85+
}
86+
return co, nil
87+
}
88+
89+
func (fc *fileCollector) matchPatterns(path string, regexps []*regexp.Regexp) bool {
90+
path = strings.ReplaceAll(path, "\\", "/")
91+
for _, re := range regexps {
92+
if re.MatchString(path) {
93+
return true
94+
}
95+
}
96+
return false
97+
}
98+
99+
func (fc *fileCollector) collectFiles() (res [][]string, err error) {
100+
var batch []string
101+
for _, dir := range fc.dirs {
102+
err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
103+
include := len(fc.includePatterns) == 0 || fc.matchPatterns(path, fc.includePatterns)
104+
exclude := fc.matchPatterns(path, fc.excludePatterns)
105+
process := include && !exclude
106+
if !process {
107+
if d.IsDir() {
108+
if exclude {
109+
logVerbose("exclude dir %s", path)
110+
return filepath.SkipDir
111+
}
112+
// for a directory, if it is not excluded explicitly, we should walk into
113+
return nil
114+
}
115+
// for a file, we skip it if it shouldn't be processed
116+
logVerbose("skip process %s", path)
117+
return nil
118+
}
119+
if d.IsDir() {
120+
// skip dir, we don't add dirs to the file list now
121+
return nil
122+
}
123+
if len(batch) >= fc.batchSize {
124+
res = append(res, batch)
125+
batch = nil
126+
}
127+
batch = append(batch, path)
128+
return nil
129+
})
130+
if err != nil {
131+
return nil, err
132+
}
133+
}
134+
res = append(res, batch)
135+
return res, nil
136+
}
137+
138+
// substArgFiles expands the {file-list} to a real file list for commands
139+
func substArgFiles(args []string, files []string) []string {
140+
for i, s := range args {
141+
if s == "{file-list}" {
142+
newArgs := append(args[:i], files...)
143+
newArgs = append(newArgs, args[i+1:]...)
144+
return newArgs
145+
}
146+
}
147+
return args
148+
}
149+
150+
func exitWithCmdErrors(subCmd string, subArgs []string, cmdErrors []error) {
151+
for _, err := range cmdErrors {
152+
if err != nil {
153+
if exitError, ok := err.(*exec.ExitError); ok {
154+
exitCode := exitError.ExitCode()
155+
log.Printf("run command failed (code=%d): %s %v", exitCode, subCmd, subArgs)
156+
os.Exit(exitCode)
157+
} else {
158+
log.Fatalf("run command failed (err=%s) %s %v", err, subCmd, subArgs)
159+
}
160+
}
161+
}
162+
}
163+
164+
func parseArgs() (mainOptions map[string]string, subCmd string, subArgs []string) {
165+
mainOptions = map[string]string{}
166+
for i := 1; i < len(os.Args); i++ {
167+
arg := os.Args[i]
168+
if arg == "" {
169+
break
170+
}
171+
if arg[0] == '-' {
172+
arg = strings.TrimPrefix(arg, "-")
173+
arg = strings.TrimPrefix(arg, "-")
174+
fields := strings.SplitN(arg, "=", 2)
175+
if len(fields) == 1 {
176+
mainOptions[fields[0]] = "1"
177+
} else {
178+
mainOptions[fields[0]] = fields[1]
179+
}
180+
} else {
181+
subCmd = arg
182+
subArgs = os.Args[i+1:]
183+
break
184+
}
185+
}
186+
return
187+
}
188+
189+
func showUsage() {
190+
fmt.Printf(`Usage: %[1]s [options] {command} [arguments]
191+
192+
Options:
193+
--verbose
194+
--file-filter=go-own
195+
--batch-size=100
196+
197+
Commands:
198+
%[1]s gofmt ...
199+
%[1]s misspell ...
200+
201+
Arguments:
202+
{file-list} the file list
203+
204+
Example:
205+
%[1]s gofmt -s -d {file-list}
206+
207+
`, "file-batch-exec")
208+
}
209+
210+
func newFileCollectorFromMainOptions(mainOptions map[string]string) (fc *fileCollector, err error) {
211+
fileFilter := mainOptions["file-filter"]
212+
if fileFilter == "" {
213+
fileFilter = "go-own"
214+
}
215+
batchSize, _ := strconv.Atoi(mainOptions["batch-size"])
216+
if batchSize == 0 {
217+
batchSize = 100
218+
}
219+
220+
return newFileCollector(fileFilter, batchSize)
221+
}
222+
223+
func containsString(a []string, s string) bool {
224+
for _, v := range a {
225+
if v == s {
226+
return true
227+
}
228+
}
229+
return false
230+
}
231+
232+
func giteaFormatGoImports(files []string) error {
233+
for _, file := range files {
234+
if err := codeformat.FormatGoImports(file); err != nil {
235+
log.Printf("failed to format go imports: %s, err=%v", file, err)
236+
return err
237+
}
238+
}
239+
return nil
240+
}
241+
242+
func main() {
243+
mainOptions, subCmd, subArgs := parseArgs()
244+
if subCmd == "" {
245+
showUsage()
246+
os.Exit(1)
247+
}
248+
optionLogVerbose = mainOptions["verbose"] != ""
249+
250+
fc, err := newFileCollectorFromMainOptions(mainOptions)
251+
if err != nil {
252+
log.Fatalf("can not create file collector: %s", err.Error())
253+
}
254+
255+
fileBatches, err := fc.collectFiles()
256+
if err != nil {
257+
log.Fatalf("can not collect files: %s", err.Error())
258+
}
259+
260+
processed := 0
261+
var cmdErrors []error
262+
for _, files := range fileBatches {
263+
if len(files) == 0 {
264+
break
265+
}
266+
substArgs := substArgFiles(subArgs, files)
267+
logVerbose("batch cmd: %s %v", subCmd, substArgs)
268+
switch subCmd {
269+
case "gitea-fmt":
270+
cmdErrors = append(cmdErrors, passThroughCmd("gofmt", substArgs))
271+
if containsString(subArgs, "-w") {
272+
cmdErrors = append(cmdErrors, giteaFormatGoImports(files))
273+
}
274+
case "misspell":
275+
cmdErrors = append(cmdErrors, passThroughCmd("misspell", substArgs))
276+
default:
277+
log.Fatalf("unknown cmd: %s %v", subCmd, subArgs)
278+
}
279+
processed += len(files)
280+
}
281+
282+
logVerbose("processed %d files", processed)
283+
exitWithCmdErrors(subCmd, subArgs, cmdErrors)
284+
}

0 commit comments

Comments
 (0)