You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

code-batch-process.go 6.9KB

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