mirror of
https://github.com/golangci/golangci-lint-action.git
synced 2025-12-12 14:01:14 +00:00
feat: support Module Plugin System (#1306)
This commit is contained in:
parent
a66d26a465
commit
043b1b8d1c
13 changed files with 20734 additions and 3090 deletions
32
.github/workflows/test.yml
vendored
32
.github/workflows/test.yml
vendored
|
|
@ -114,3 +114,35 @@ jobs:
|
||||||
with:
|
with:
|
||||||
working-directory: ${{ matrix.wd }}
|
working-directory: ${{ matrix.wd }}
|
||||||
args: --timeout=5m --issues-exit-code=0 ./...
|
args: --timeout=5m --issues-exit-code=0 ./...
|
||||||
|
|
||||||
|
test-plugins: # make sure the action works on a clean machine with plugins
|
||||||
|
needs: [ build ]
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- ubuntu-latest
|
||||||
|
- ubuntu-22.04-arm
|
||||||
|
- macos-latest
|
||||||
|
- windows-latest
|
||||||
|
version:
|
||||||
|
- ""
|
||||||
|
- "latest"
|
||||||
|
- "v2.5"
|
||||||
|
- "v2.5.0"
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: 24.x
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version: oldstable
|
||||||
|
- uses: ./
|
||||||
|
with:
|
||||||
|
version: ${{ matrix.version }}
|
||||||
|
working-directory: sample-plugins
|
||||||
|
args: --timeout=5m --issues-exit-code=0 ./...
|
||||||
|
|
|
||||||
|
|
@ -548,6 +548,13 @@ permissions:
|
||||||
|
|
||||||
For annotations to work, use the default format output (`text`) and either use [`actions/setup-go`](https://github.com/actions/setup-go) in the job or enable the internal [problem matchers](#problem-matchers).
|
For annotations to work, use the default format output (`text`) and either use [`actions/setup-go`](https://github.com/actions/setup-go) in the job or enable the internal [problem matchers](#problem-matchers).
|
||||||
|
|
||||||
|
## Module Plugin System
|
||||||
|
|
||||||
|
The action will automatically detect the custom build configuration file `.custom-gcl.yml`,
|
||||||
|
build and run the custom version of golangci-lint.
|
||||||
|
|
||||||
|
For more information, see [module plugin system](https://golangci-lint.run/docs/plugins/module-plugins/).
|
||||||
|
|
||||||
## Performance
|
## Performance
|
||||||
|
|
||||||
The action was implemented with performance in mind:
|
The action was implemented with performance in mind:
|
||||||
|
|
|
||||||
11803
dist/post_run/index.js
generated
vendored
11803
dist/post_run/index.js
generated
vendored
File diff suppressed because one or more lines are too long
11803
dist/run/index.js
generated
vendored
11803
dist/run/index.js
generated
vendored
File diff suppressed because one or more lines are too long
15
package-lock.json
generated
15
package-lock.json
generated
|
|
@ -21,7 +21,8 @@
|
||||||
"@types/tmp": "^0.2.6",
|
"@types/tmp": "^0.2.6",
|
||||||
"@types/which": "^3.0.4",
|
"@types/which": "^3.0.4",
|
||||||
"tmp": "^0.2.5",
|
"tmp": "^0.2.5",
|
||||||
"which": "^5.0.0"
|
"which": "^5.0.0",
|
||||||
|
"yaml": "^2.8.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
||||||
|
|
@ -4408,6 +4409,18 @@
|
||||||
"node": ">=4.0"
|
"node": ">=4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/yaml": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"yaml": "bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yocto-queue": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,8 @@
|
||||||
"@types/tmp": "^0.2.6",
|
"@types/tmp": "^0.2.6",
|
||||||
"@types/which": "^3.0.4",
|
"@types/which": "^3.0.4",
|
||||||
"tmp": "^0.2.5",
|
"tmp": "^0.2.5",
|
||||||
"which": "^5.0.0"
|
"which": "^5.0.0",
|
||||||
|
"yaml": "^2.8.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
||||||
|
|
|
||||||
7
sample-plugins/.custom-gcl.yml
Normal file
7
sample-plugins/.custom-gcl.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
version: v2.6.1
|
||||||
|
name: custom-golangci-lint
|
||||||
|
#destination: ./zzz/path/
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- module: 'github.com/golangci/example-plugin-module-linter'
|
||||||
|
version: v0.1.0
|
||||||
1
sample-plugins/.gitignore
vendored
Normal file
1
sample-plugins/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/custom-golangci-lint
|
||||||
25
sample-plugins/.golangci.yml
Normal file
25
sample-plugins/.golangci.yml
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
version: "2"
|
||||||
|
|
||||||
|
linters:
|
||||||
|
default: none
|
||||||
|
enable:
|
||||||
|
- example
|
||||||
|
|
||||||
|
settings:
|
||||||
|
custom:
|
||||||
|
example:
|
||||||
|
type: module
|
||||||
|
# Description is optional
|
||||||
|
description: The description of the linter. This is optional, but shows up when running `golangci-lint linters`.
|
||||||
|
# Original-url is optional, and is only used for documentation purposes.
|
||||||
|
original-url: github.com/golangci/example-plugin-module-linter
|
||||||
|
settings:
|
||||||
|
one: Foo
|
||||||
|
two:
|
||||||
|
- name: Bar
|
||||||
|
three:
|
||||||
|
name: Bar
|
||||||
|
|
||||||
|
issues:
|
||||||
|
max-issues-per-linter: 0
|
||||||
|
max-same-issues: 0
|
||||||
3
sample-plugins/go.mod
Normal file
3
sample-plugins/go.mod
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
module github.com/golangci/sample
|
||||||
|
|
||||||
|
go 1.24.0
|
||||||
22
sample-plugins/sample.go
Normal file
22
sample-plugins/sample.go
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Package sample is used as test input for golangci action.
|
||||||
|
package sample
|
||||||
|
|
||||||
|
// comment without a to do
|
||||||
|
func SomeFunc1() {
|
||||||
|
_ = 1 + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: do something // want "TODO comment has no author"
|
||||||
|
func SomeFunc2() {
|
||||||
|
_ = 1 + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(): do something // want "TODO comment has no author"
|
||||||
|
func SomeFunc3() {
|
||||||
|
_ = 1 + 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(dbraley): Do something with the value
|
||||||
|
func SomeFunc4() {
|
||||||
|
_ = 1 + 4
|
||||||
|
}
|
||||||
90
src/plugins.ts
Normal file
90
src/plugins.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
import * as core from "@actions/core"
|
||||||
|
import { exec, ExecOptionsWithStringEncoding } from "child_process"
|
||||||
|
import * as fs from "fs"
|
||||||
|
import * as path from "path"
|
||||||
|
import { promisify } from "util"
|
||||||
|
import YAML from "yaml"
|
||||||
|
|
||||||
|
const execShellCommand = promisify(exec)
|
||||||
|
|
||||||
|
type ExecRes = {
|
||||||
|
stdout: string
|
||||||
|
stderr: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const printOutput = (res: ExecRes): void => {
|
||||||
|
if (res.stdout) {
|
||||||
|
core.info(res.stdout)
|
||||||
|
}
|
||||||
|
if (res.stderr) {
|
||||||
|
core.info(res.stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function install(binPath: string): Promise<string> {
|
||||||
|
let rootDir = core.getInput(`working-directory`)
|
||||||
|
|
||||||
|
if (rootDir) {
|
||||||
|
if (!fs.existsSync(rootDir) || !fs.lstatSync(rootDir).isDirectory()) {
|
||||||
|
throw new Error(`working-directory (${rootDir}) was not a path`)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootDir = path.resolve(rootDir)
|
||||||
|
} else {
|
||||||
|
rootDir = process.cwd()
|
||||||
|
}
|
||||||
|
|
||||||
|
const configFile = [".custom-gcl.yml", ".custom-gcl.yaml", ".custom-gcl.json"]
|
||||||
|
.map((v) => path.join(rootDir, v))
|
||||||
|
.find((filePath) => fs.existsSync(filePath))
|
||||||
|
|
||||||
|
if (!configFile || configFile === "") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
core.info(`Found configuration for the plugin module system : ${configFile}`)
|
||||||
|
|
||||||
|
core.info(`Building and installing custom golangci-lint binary...`)
|
||||||
|
|
||||||
|
const startedAt = Date.now()
|
||||||
|
|
||||||
|
const config = YAML.parse(fs.readFileSync(configFile, "utf-8"))
|
||||||
|
|
||||||
|
const v: string = core.getInput(`version`)
|
||||||
|
if (v !== "" && config.version !== v) {
|
||||||
|
core.warning(
|
||||||
|
`The golangci-lint version (${config.version}) defined inside in ${configFile} does not match the version defined in the action (${v})`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.destination) {
|
||||||
|
config.destination = "."
|
||||||
|
}
|
||||||
|
if (!config.name) {
|
||||||
|
config.name = "custom-gcl"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(config.destination)) {
|
||||||
|
core.info(`Creating destination directory: ${config.destination}`)
|
||||||
|
fs.mkdirSync(config.destination, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const cmd = `${binPath} custom`
|
||||||
|
|
||||||
|
core.info(`Running [${cmd}] in [${rootDir}] ...`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options: ExecOptionsWithStringEncoding = {
|
||||||
|
cwd: rootDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await execShellCommand(cmd, options)
|
||||||
|
printOutput(res)
|
||||||
|
|
||||||
|
core.info(`Built custom golangci-lint binary in ${Date.now() - startedAt}ms`)
|
||||||
|
|
||||||
|
return path.join(rootDir, config.destination, config.name)
|
||||||
|
} catch (exc) {
|
||||||
|
throw new Error(`Failed to build custom golangci-lint binary: ${exc.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/run.ts
13
src/run.ts
|
|
@ -8,6 +8,7 @@ import { promisify } from "util"
|
||||||
import { restoreCache, saveCache } from "./cache"
|
import { restoreCache, saveCache } from "./cache"
|
||||||
import { install } from "./install"
|
import { install } from "./install"
|
||||||
import { fetchPatch, isOnlyNewIssues } from "./patch"
|
import { fetchPatch, isOnlyNewIssues } from "./patch"
|
||||||
|
import * as plugins from "./plugins"
|
||||||
|
|
||||||
const execShellCommand = promisify(exec)
|
const execShellCommand = promisify(exec)
|
||||||
|
|
||||||
|
|
@ -22,7 +23,13 @@ async function prepareEnv(installOnly: boolean): Promise<Env> {
|
||||||
// Prepare cache, lint and go in parallel.
|
// Prepare cache, lint and go in parallel.
|
||||||
await restoreCache()
|
await restoreCache()
|
||||||
|
|
||||||
const binPath = await install()
|
let binPath = await install()
|
||||||
|
|
||||||
|
// Build custom golangci-lint if needed.
|
||||||
|
const customBinPath = await plugins.install(binPath)
|
||||||
|
if (customBinPath !== "") {
|
||||||
|
binPath = customBinPath
|
||||||
|
}
|
||||||
|
|
||||||
if (installOnly) {
|
if (installOnly) {
|
||||||
return { binPath, patchPath: `` }
|
return { binPath, patchPath: `` }
|
||||||
|
|
@ -203,9 +210,7 @@ export async function run(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const installOnly = core.getBooleanInput(`install-only`, { required: true })
|
const installOnly = core.getBooleanInput(`install-only`, { required: true })
|
||||||
|
|
||||||
const { binPath, patchPath } = await core.group(`prepare environment`, () => {
|
const { binPath, patchPath } = await core.group(`prepare environment`, () => prepareEnv(installOnly))
|
||||||
return prepareEnv(installOnly)
|
|
||||||
})
|
|
||||||
|
|
||||||
core.addPath(path.dirname(binPath))
|
core.addPath(path.dirname(binPath))
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue