Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

code-batch-process.go 7.2KB

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