From a5d81d9ce230aaa6e1021b6236ca01cb6d2b56c3 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 8 May 2026 23:39:35 +0200 Subject: [PATCH] perf: replace `goheader` linter with custom check (#37599) Replace the [slow `goheader` linter](https://github.com/denis-tingaikin/go-header/issues/70) with a custom check. Local go lint time is down from 247s to 32s. 6 new files that were previously undetected because of `//go:build ignore` are fixed. The exit code of the make target preserves the golangci-lint exit code, if present. Also refactors and consolidates the linting targets. Signed-off-by: silverwind Signed-off-by: wxiaoguang Co-authored-by: Claude (Opus 4.7) Co-authored-by: wxiaoguang Co-authored-by: Giteabot --- .github/workflows/cache-seeder.yml | 2 - .github/workflows/pull-compliance.yml | 42 ----------- .golangci.yml | 6 -- Makefile | 11 +-- build/generate-gitignores.go | 3 + models/db/driver_sqlite_mattn.go | 4 +- models/db/driver_sqlite_modernc.go | 4 +- modules/auth/pam/pam_test.go | 4 +- tools/lint-go-all.go | 101 ++++++++++++++++++++++++++ 9 files changed, 112 insertions(+), 65 deletions(-) create mode 100644 tools/lint-go-all.go diff --git a/.github/workflows/cache-seeder.yml b/.github/workflows/cache-seeder.yml index b0f3ca97de..733077cc80 100644 --- a/.github/workflows/cache-seeder.yml +++ b/.github/workflows/cache-seeder.yml @@ -54,8 +54,6 @@ jobs: matrix: include: - { job: lint-backend, tags: "bindata", target: "lint-backend" } - - { job: lint-go-windows, tags: "bindata", target: "lint-go-windows" } - - { job: lint-go-gogit, tags: "bindata gogit", target: "lint-go" } steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index 6a3ec2d73e..51e1c805e6 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -64,48 +64,6 @@ jobs: - if: needs.files-changed.outputs.actions == 'true' run: make lint-actions - lint-go-windows: - if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' - needs: files-changed - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version-file: go.mod - check-latest: true - cache: false - - uses: ./.github/actions/go-cache - with: - cache-name: lint-go-windows - lint-cache: "true" - - run: make deps-backend deps-tools - - run: make lint-go-windows - env: - TAGS: bindata - GOOS: windows - GOARCH: amd64 - - lint-go-gogit: - if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' - needs: files-changed - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version-file: go.mod - check-latest: true - cache: false - - uses: ./.github/actions/go-cache - with: - cache-name: lint-go-gogit - lint-cache: "true" - - run: make deps-backend deps-tools - - run: make lint-go - env: - TAGS: bindata gogit - checks-backend: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed diff --git a/.golangci.yml b/.golangci.yml index 570942bdd3..056a6c2a08 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -13,7 +13,6 @@ linters: - forbidigo - gocheckcompilerdirectives - gocritic - - goheader - govet - ineffassign - mirror @@ -118,11 +117,6 @@ linters: enable: - nilness - unusedwrite - goheader: - values: - regexp: - HEADER: '((Copyright [^\n]+|All rights reserved\.)\n)*Copyright \d{4} (The (Gogs|Gitea) Authors|Gitea Authors|Gitea)\.( All rights reserved\.)?(\n(Copyright [^\n]+|All rights reserved\.))*\nSPDX-License-Identifier: [\w.-]+' - template: '{{ HEADER }}' exclusions: generated: lax presets: diff --git a/Makefile b/Makefile index 1087dc8410..27b2c30295 100644 --- a/Makefile +++ b/Makefile @@ -332,18 +332,11 @@ lint-spell-fix: ## lint spelling and fix issues .PHONY: lint-go lint-go: ## lint go files - $(GO) run $(GOLANGCI_LINT_PACKAGE) run + GO=$(GO) GOLANGCI_LINT_PACKAGE=$(GOLANGCI_LINT_PACKAGE) $(GO) run ./tools/lint-go-all.go .PHONY: lint-go-fix lint-go-fix: ## lint go files and fix issues - $(GO) run $(GOLANGCI_LINT_PACKAGE) run --fix - -# workaround step for the lint-go-windows CI task because 'go run' can not -# have distinct GOOS/GOARCH for its build and run steps -.PHONY: lint-go-windows -lint-go-windows: - @GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE) - golangci-lint run + GO=$(GO) GOLANGCI_LINT_PACKAGE=$(GOLANGCI_LINT_PACKAGE) $(GO) run ./tools/lint-go-all.go --fix .PHONY: lint-editorconfig lint-editorconfig: diff --git a/build/generate-gitignores.go b/build/generate-gitignores.go index 1e09c83a6a..7a498f548e 100644 --- a/build/generate-gitignores.go +++ b/build/generate-gitignores.go @@ -1,3 +1,6 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + //go:build ignore package main diff --git a/models/db/driver_sqlite_mattn.go b/models/db/driver_sqlite_mattn.go index 1fe2e35cba..a25cb09990 100644 --- a/models/db/driver_sqlite_mattn.go +++ b/models/db/driver_sqlite_mattn.go @@ -1,8 +1,8 @@ -//go:build sqlite_mattn && sqlite_unlock_notify - // Copyright 2026 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT +//go:build sqlite_mattn && sqlite_unlock_notify + package db import ( diff --git a/models/db/driver_sqlite_modernc.go b/models/db/driver_sqlite_modernc.go index 8202d5f6f4..6bd5498a55 100644 --- a/models/db/driver_sqlite_modernc.go +++ b/models/db/driver_sqlite_modernc.go @@ -1,8 +1,8 @@ -//go:build !sqlite_mattn - // Copyright 2026 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT +//go:build !sqlite_mattn + // modernc driver is chosen as the default one (compared to mattn, ncruces) // * mattn was used as default, but it requires CGO // * the CI times are almost the same for these three (race detector must be disabled) diff --git a/modules/auth/pam/pam_test.go b/modules/auth/pam/pam_test.go index d4ab058ec7..1180e75d88 100644 --- a/modules/auth/pam/pam_test.go +++ b/modules/auth/pam/pam_test.go @@ -1,8 +1,8 @@ -//go:build pam - // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT +//go:build pam + package pam import ( diff --git a/tools/lint-go-all.go b/tools/lint-go-all.go new file mode 100644 index 0000000000..b2071f1703 --- /dev/null +++ b/tools/lint-go-all.go @@ -0,0 +1,101 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package main + +import ( + "fmt" + "io" + "io/fs" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" +) + +func lintGoHeader() bool { + headerRE := regexp.MustCompile(`^(// (Copyright [^\n]+|All rights reserved\.)\n)*// Copyright \d{4} (The Gogs Authors|The Gitea Authors|Gitea Authors|Gitea)\.( All rights reserved\.)?\n(// (Copyright [^\n]+|All rights reserved\.)\n)*// SPDX-License-Identifier: [\w.-]+`) + generatedRE := regexp.MustCompile(`(?m)^// (Code|This file is) [Gg]enerated.*DO NOT EDIT`) + skipDirs := map[string]bool{ + ".git": true, + ".venv": true, + "node_modules": true, + "public": true, + "vendor": true, + "web_src": true, + } + root, bad := ".", 0 + err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + if rel, _ := filepath.Rel(root, path); skipDirs[filepath.ToSlash(rel)] { + return fs.SkipDir + } + return nil + } + if !strings.HasSuffix(path, ".go") { + return nil + } + f, err := os.Open(path) + if err != nil { + return err + } + data, err := io.ReadAll(io.LimitReader(f, 512)) + _ = f.Close() + if err != nil { + return err + } + if generatedRE.Match(data) { + return nil + } + if !headerRE.Match(data) { + _, _ = fmt.Fprintf(os.Stderr, "%s: missing or invalid copyright header\n", path) + bad++ + } + return nil + }) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err) + } + return err == nil && bad == 0 +} + +func runCmd(env []string, name string, args []string) bool { + cmd := exec.Command(name, args...) + cmd.Env = append(os.Environ(), env...) + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + _, _ = fmt.Fprintln(os.Stderr, err) + return false + } + return true +} + +func main() { + // 'go run' can not have distinct GOOS/GOARCH for its build and run steps, + // so install a pre-compiled binary and run it for different target platforms. + _, _ = os.Unsetenv("GOOS"), os.Unsetenv("GOARCH") + + envGolangciLintPackage := os.Getenv("GOLANGCI_LINT_PACKAGE") + envGo := os.Getenv("GO") + if envGo == "" || envGolangciLintPackage == "" { + _, _ = fmt.Fprintln(os.Stderr, "Environment variables GO and GOLANGCI_LINT_PACKAGE must be set") + os.Exit(1) + } + if !runCmd(nil, envGo, []string{"install", envGolangciLintPackage}) { + os.Exit(1) + } + + _, _ = fmt.Fprintln(os.Stdout, "lint go header ...") + succeed := lintGoHeader() + _, _ = fmt.Fprintln(os.Stdout, "lint for linux ...") + succeed = runCmd([]string{"GOOS=linux", "TAGS=bindata"}, "golangci-lint", append([]string{"run"}, os.Args[1:]...)) && succeed + _, _ = fmt.Fprintln(os.Stdout, "lint for windows ...") + succeed = runCmd([]string{"GOOS=windows", "TAGS=gogit"}, "golangci-lint", append([]string{"run"}, os.Args[1:]...)) && succeed + if !succeed { + os.Exit(1) + } +}