|
| 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