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