mirror of
https://github.com/scratchfoundation/golangci-lint.git
synced 2025-08-13 14:58:45 -04:00
dev: clean up Executor (#4404)
This commit is contained in:
parent
4068bb73fe
commit
8c51979ec8
21 changed files with 716 additions and 809 deletions
|
@ -1,13 +1,19 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/golangci/golangci-lint/internal/cache"
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||
)
|
||||
|
@ -21,14 +27,13 @@ func (e *Executor) initCache() {
|
|||
return cmd.Help()
|
||||
},
|
||||
}
|
||||
e.rootCmd.AddCommand(cacheCmd)
|
||||
|
||||
cacheCmd.AddCommand(&cobra.Command{
|
||||
Use: "clean",
|
||||
Short: "Clean cache",
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
RunE: e.executeCleanCache,
|
||||
RunE: e.executeCacheClean,
|
||||
})
|
||||
cacheCmd.AddCommand(&cobra.Command{
|
||||
Use: "status",
|
||||
|
@ -39,9 +44,11 @@ func (e *Executor) initCache() {
|
|||
})
|
||||
|
||||
// TODO: add trim command?
|
||||
|
||||
e.rootCmd.AddCommand(cacheCmd)
|
||||
}
|
||||
|
||||
func (e *Executor) executeCleanCache(_ *cobra.Command, _ []string) error {
|
||||
func (e *Executor) executeCacheClean(_ *cobra.Command, _ []string) error {
|
||||
cacheDir := cache.DefaultDir()
|
||||
if err := os.RemoveAll(cacheDir); err != nil {
|
||||
return fmt.Errorf("failed to remove dir %s: %w", cacheDir, err)
|
||||
|
@ -70,3 +77,68 @@ func dirSizeBytes(path string) (int64, error) {
|
|||
})
|
||||
return size, err
|
||||
}
|
||||
|
||||
// --- Related to cache but not used directly by the cache command.
|
||||
|
||||
func initHashSalt(version string, cfg *config.Config) error {
|
||||
binSalt, err := computeBinarySalt(version)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to calculate binary salt: %w", err)
|
||||
}
|
||||
|
||||
configSalt, err := computeConfigSalt(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to calculate config salt: %w", err)
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(binSalt)
|
||||
b.Write(configSalt)
|
||||
cache.SetSalt(b.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
func computeBinarySalt(version string) ([]byte, error) {
|
||||
if version != "" && version != "(devel)" {
|
||||
return []byte(version), nil
|
||||
}
|
||||
|
||||
if logutils.HaveDebugTag(logutils.DebugKeyBinSalt) {
|
||||
return []byte("debug"), nil
|
||||
}
|
||||
|
||||
p, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := os.Open(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.Sum(nil), nil
|
||||
}
|
||||
|
||||
// computeConfigSalt computes configuration hash.
|
||||
// We don't hash all config fields to reduce meaningless cache invalidations.
|
||||
// At least, it has a huge impact on tests speed.
|
||||
// Fields: `LintersSettings` and `Run.BuildTags`.
|
||||
func computeConfigSalt(cfg *config.Config) ([]byte, error) {
|
||||
lintersSettingsBytes, err := yaml.Marshal(cfg.LintersSettings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to json marshal config linter settings: %w", err)
|
||||
}
|
||||
|
||||
configData := bytes.NewBufferString("linters-settings=")
|
||||
configData.Write(lintersSettingsBytes)
|
||||
configData.WriteString("\nbuild-tags=%s" + strings.Join(cfg.Run.BuildTags, ","))
|
||||
|
||||
h := sha256.New()
|
||||
if _, err := h.Write(configData.Bytes()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.Sum(nil), nil
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
)
|
||||
|
||||
func (e *Executor) initConfig() {
|
||||
cmd := &cobra.Command{
|
||||
configCmd := &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Config file information",
|
||||
Args: cobra.NoArgs,
|
||||
|
@ -22,26 +22,34 @@ func (e *Executor) initConfig() {
|
|||
return cmd.Help()
|
||||
},
|
||||
}
|
||||
e.rootCmd.AddCommand(cmd)
|
||||
|
||||
pathCmd := &cobra.Command{
|
||||
Use: "path",
|
||||
Short: "Print used config path",
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
Run: e.executePathCmd,
|
||||
Run: e.executePath,
|
||||
}
|
||||
|
||||
fs := pathCmd.Flags()
|
||||
fs.SortFlags = false // sort them as they are defined here
|
||||
|
||||
initConfigFileFlagSet(fs, &e.cfg.Run)
|
||||
|
||||
cmd.AddCommand(pathCmd)
|
||||
configCmd.AddCommand(pathCmd)
|
||||
e.rootCmd.AddCommand(configCmd)
|
||||
}
|
||||
|
||||
// getUsedConfig returns the resolved path to the golangci config file, or the empty string
|
||||
// if no configuration could be found.
|
||||
func (e *Executor) executePath(_ *cobra.Command, _ []string) {
|
||||
usedConfigFile := e.getUsedConfig()
|
||||
if usedConfigFile == "" {
|
||||
e.log.Warnf("No config file detected")
|
||||
os.Exit(exitcodes.NoConfigFileDetected)
|
||||
}
|
||||
|
||||
fmt.Println(usedConfigFile)
|
||||
}
|
||||
|
||||
// getUsedConfig returns the resolved path to the golangci config file,
|
||||
// or the empty string if no configuration could be found.
|
||||
func (e *Executor) getUsedConfig() string {
|
||||
usedConfigFile := viper.ConfigFileUsed()
|
||||
if usedConfigFile == "" {
|
||||
|
@ -57,15 +65,7 @@ func (e *Executor) getUsedConfig() string {
|
|||
return prettyUsedConfigFile
|
||||
}
|
||||
|
||||
func (e *Executor) executePathCmd(_ *cobra.Command, _ []string) {
|
||||
usedConfigFile := e.getUsedConfig()
|
||||
if usedConfigFile == "" {
|
||||
e.log.Warnf("No config file detected")
|
||||
os.Exit(exitcodes.NoConfigFileDetected)
|
||||
}
|
||||
|
||||
fmt.Println(usedConfigFile)
|
||||
}
|
||||
// --- Related to config but not used directly by the config command.
|
||||
|
||||
func initConfigFileFlagSet(fs *pflag.FlagSet, cfg *config.Run) {
|
||||
fs.StringVarP(&cfg.Config, "config", "c", "", wh("Read config from file path `PATH`"))
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -16,9 +11,7 @@ import (
|
|||
"github.com/gofrs/flock"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/golangci/golangci-lint/internal/cache"
|
||||
"github.com/golangci/golangci-lint/internal/pkgcache"
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||
|
@ -31,54 +24,71 @@ import (
|
|||
"github.com/golangci/golangci-lint/pkg/timeutils"
|
||||
)
|
||||
|
||||
type BuildInfo struct {
|
||||
GoVersion string `json:"goVersion"`
|
||||
Version string `json:"version"`
|
||||
Commit string `json:"commit"`
|
||||
Date string `json:"date"`
|
||||
}
|
||||
|
||||
type Executor struct {
|
||||
rootCmd *cobra.Command
|
||||
runCmd *cobra.Command
|
||||
lintersCmd *cobra.Command
|
||||
rootCmd *cobra.Command
|
||||
|
||||
runCmd *cobra.Command // used by fixSlicesFlags, printStats
|
||||
lintersCmd *cobra.Command // used by fixSlicesFlags
|
||||
|
||||
exitCode int
|
||||
|
||||
exitCode int
|
||||
buildInfo BuildInfo
|
||||
|
||||
cfg *config.Config // cfg is the unmarshaled data from the golangci config file.
|
||||
log logutils.Log
|
||||
reportData report.Data
|
||||
DBManager *lintersdb.Manager
|
||||
EnabledLintersSet *lintersdb.EnabledSet
|
||||
contextLoader *lint.ContextLoader
|
||||
goenv *goutil.Env
|
||||
fileCache *fsutils.FileCache
|
||||
lineCache *fsutils.LineCache
|
||||
pkgCache *pkgcache.Cache
|
||||
debugf logutils.DebugFunc
|
||||
sw *timeutils.Stopwatch
|
||||
cfg *config.Config // cfg is the unmarshaled data from the golangci config file.
|
||||
|
||||
loadGuard *load.Guard
|
||||
flock *flock.Flock
|
||||
log logutils.Log
|
||||
debugf logutils.DebugFunc
|
||||
reportData report.Data
|
||||
|
||||
dbManager *lintersdb.Manager
|
||||
enabledLintersSet *lintersdb.EnabledSet
|
||||
|
||||
contextLoader *lint.ContextLoader
|
||||
goenv *goutil.Env
|
||||
|
||||
fileCache *fsutils.FileCache
|
||||
lineCache *fsutils.LineCache
|
||||
|
||||
flock *flock.Flock
|
||||
}
|
||||
|
||||
// NewExecutor creates and initializes a new command executor.
|
||||
func NewExecutor(buildInfo BuildInfo) *Executor {
|
||||
startedAt := time.Now()
|
||||
e := &Executor{
|
||||
cfg: config.NewDefault(),
|
||||
buildInfo: buildInfo,
|
||||
DBManager: lintersdb.NewManager(nil, nil),
|
||||
debugf: logutils.Debug(logutils.DebugKeyExec),
|
||||
}
|
||||
|
||||
e.debugf("Starting execution...")
|
||||
e.log = report.NewLogWrapper(logutils.NewStderrLog(logutils.DebugKeyEmpty), &e.reportData)
|
||||
|
||||
// to setup log level early we need to parse config from command line extra time to
|
||||
// find `-v` option
|
||||
commandLineCfg, err := e.getConfigForCommandLine()
|
||||
// init of commands must be done before config file reading because init sets config with the default values of flags.
|
||||
e.initCommands()
|
||||
|
||||
startedAt := time.Now()
|
||||
e.debugf("Starting execution...")
|
||||
|
||||
e.initConfiguration()
|
||||
e.initExecutor()
|
||||
|
||||
e.debugf("Initialized executor in %s", time.Since(startedAt))
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *Executor) initCommands() {
|
||||
e.initRoot()
|
||||
e.initRun()
|
||||
e.initHelp()
|
||||
e.initLinters()
|
||||
e.initConfig()
|
||||
e.initVersion()
|
||||
e.initCache()
|
||||
}
|
||||
|
||||
func (e *Executor) initConfiguration() {
|
||||
// to set up log level early we need to parse config from command line extra time to find `-v` option.
|
||||
commandLineCfg, err := getConfigForCommandLine()
|
||||
if err != nil && !errors.Is(err, pflag.ErrHelp) {
|
||||
e.log.Fatalf("Can't get config for command line: %s", err)
|
||||
}
|
||||
|
@ -97,19 +107,8 @@ func NewExecutor(buildInfo BuildInfo) *Executor {
|
|||
}
|
||||
}
|
||||
|
||||
// init of commands must be done before config file reading because
|
||||
// init sets config with the default values of flags
|
||||
e.initRoot()
|
||||
e.initRun()
|
||||
e.initHelp()
|
||||
e.initLinters()
|
||||
e.initConfig()
|
||||
e.initVersion()
|
||||
e.initCache()
|
||||
|
||||
// init e.cfg by values from config: flags parse will see these values
|
||||
// like the default ones. It will overwrite them only if the same option
|
||||
// is found in command-line: it's ok, command-line has higher priority.
|
||||
// init e.cfg by values from config: flags parse will see these values like the default ones.
|
||||
// It will overwrite them only if the same option is found in command-line: it's ok, command-line has higher priority.
|
||||
|
||||
r := config.NewFileReader(e.cfg, commandLineCfg, e.log.Child(logutils.DebugKeyConfigReader))
|
||||
if err = r.Read(); err != nil {
|
||||
|
@ -131,135 +130,101 @@ func NewExecutor(buildInfo BuildInfo) *Executor {
|
|||
e.cfg.Run.Go = config.DetectGoVersion()
|
||||
}
|
||||
|
||||
// recreate after getting config
|
||||
e.DBManager = lintersdb.NewManager(e.cfg, e.log)
|
||||
|
||||
// Slice options must be explicitly set for proper merging of config and command-line options.
|
||||
fixSlicesFlags(e.runCmd.Flags())
|
||||
fixSlicesFlags(e.lintersCmd.Flags())
|
||||
}
|
||||
|
||||
func (e *Executor) initExecutor() {
|
||||
e.dbManager = lintersdb.NewManager(e.cfg, e.log)
|
||||
|
||||
e.enabledLintersSet = lintersdb.NewEnabledSet(e.dbManager,
|
||||
lintersdb.NewValidator(e.dbManager), e.log.Child(logutils.DebugKeyLintersDB), e.cfg)
|
||||
|
||||
e.EnabledLintersSet = lintersdb.NewEnabledSet(e.DBManager,
|
||||
lintersdb.NewValidator(e.DBManager), e.log.Child(logutils.DebugKeyLintersDB), e.cfg)
|
||||
e.goenv = goutil.NewEnv(e.log.Child(logutils.DebugKeyGoEnv))
|
||||
|
||||
e.fileCache = fsutils.NewFileCache()
|
||||
e.lineCache = fsutils.NewLineCache(e.fileCache)
|
||||
|
||||
e.sw = timeutils.NewStopwatch("pkgcache", e.log.Child(logutils.DebugKeyStopwatch))
|
||||
e.pkgCache, err = pkgcache.NewCache(e.sw, e.log.Child(logutils.DebugKeyPkgCache))
|
||||
sw := timeutils.NewStopwatch("pkgcache", e.log.Child(logutils.DebugKeyStopwatch))
|
||||
|
||||
pkgCache, err := pkgcache.NewCache(sw, e.log.Child(logutils.DebugKeyPkgCache))
|
||||
if err != nil {
|
||||
e.log.Fatalf("Failed to build packages cache: %s", err)
|
||||
}
|
||||
e.loadGuard = load.NewGuard()
|
||||
|
||||
e.contextLoader = lint.NewContextLoader(e.cfg, e.log.Child(logutils.DebugKeyLoader), e.goenv,
|
||||
e.lineCache, e.fileCache, e.pkgCache, e.loadGuard)
|
||||
if err = e.initHashSalt(buildInfo.Version); err != nil {
|
||||
e.lineCache, e.fileCache, pkgCache, load.NewGuard())
|
||||
|
||||
if err = initHashSalt(e.buildInfo.Version, e.cfg); err != nil {
|
||||
e.log.Fatalf("Failed to init hash salt: %s", err)
|
||||
}
|
||||
e.debugf("Initialized executor in %s", time.Since(startedAt))
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *Executor) Execute() error {
|
||||
return e.rootCmd.Execute()
|
||||
}
|
||||
|
||||
func (e *Executor) initHashSalt(version string) error {
|
||||
binSalt, err := computeBinarySalt(version)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to calculate binary salt: %w", err)
|
||||
func getConfigForCommandLine() (*config.Config, error) {
|
||||
// We use another pflag.FlagSet here to not set `changed` flag
|
||||
// on cmd.Flags() options. Otherwise, string slice options will be duplicated.
|
||||
fs := pflag.NewFlagSet("config flag set", pflag.ContinueOnError)
|
||||
|
||||
var cfg config.Config
|
||||
// Don't do `fs.AddFlagSet(cmd.Flags())` because it shares flags representations:
|
||||
// `changed` variable inside string slice vars will be shared.
|
||||
// Use another config variable here, not e.cfg, to not
|
||||
// affect main parsing by this parsing of only config option.
|
||||
initRunFlagSet(fs, &cfg)
|
||||
initVersionFlagSet(fs, &cfg)
|
||||
|
||||
// Parse max options, even force version option: don't want
|
||||
// to get access to Executor here: it's error-prone to use
|
||||
// cfg vs e.cfg.
|
||||
initRootFlagSet(fs, &cfg)
|
||||
|
||||
fs.Usage = func() {} // otherwise, help text will be printed twice
|
||||
if err := fs.Parse(os.Args); err != nil {
|
||||
if errors.Is(err, pflag.ErrHelp) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("can't parse args: %w", err)
|
||||
}
|
||||
|
||||
configSalt, err := computeConfigSalt(e.cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to calculate config salt: %w", err)
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(binSalt)
|
||||
b.Write(configSalt)
|
||||
cache.SetSalt(b.Bytes())
|
||||
return nil
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func computeBinarySalt(version string) ([]byte, error) {
|
||||
if version != "" && version != "(devel)" {
|
||||
return []byte(version), nil
|
||||
}
|
||||
func fixSlicesFlags(fs *pflag.FlagSet) {
|
||||
// It's a dirty hack to set flag.Changed to true for every string slice flag.
|
||||
// It's necessary to merge config and command-line slices: otherwise command-line
|
||||
// flags will always overwrite ones from the config.
|
||||
fs.VisitAll(func(f *pflag.Flag) {
|
||||
if f.Value.Type() != "stringSlice" {
|
||||
return
|
||||
}
|
||||
|
||||
if logutils.HaveDebugTag(logutils.DebugKeyBinSalt) {
|
||||
return []byte("debug"), nil
|
||||
}
|
||||
s, err := fs.GetStringSlice(f.Name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := os.Open(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.Sum(nil), nil
|
||||
if s == nil { // assume that every string slice flag has nil as the default
|
||||
return
|
||||
}
|
||||
|
||||
var safe []string
|
||||
for _, v := range s {
|
||||
// add quotes to escape comma because spf13/pflag use a CSV parser:
|
||||
// https://github.com/spf13/pflag/blob/85dd5c8bc61cfa382fecd072378089d4e856579d/string_slice.go#L43
|
||||
safe = append(safe, `"`+v+`"`)
|
||||
}
|
||||
|
||||
// calling Set sets Changed to true: next Set calls will append, not overwrite
|
||||
_ = f.Value.Set(strings.Join(safe, ","))
|
||||
})
|
||||
}
|
||||
|
||||
func computeConfigSalt(cfg *config.Config) ([]byte, error) {
|
||||
// We don't hash all config fields to reduce meaningless cache
|
||||
// invalidations. At least, it has a huge impact on tests speed.
|
||||
|
||||
lintersSettingsBytes, err := yaml.Marshal(cfg.LintersSettings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to json marshal config linter settings: %w", err)
|
||||
}
|
||||
|
||||
configData := bytes.NewBufferString("linters-settings=")
|
||||
configData.Write(lintersSettingsBytes)
|
||||
configData.WriteString("\nbuild-tags=%s" + strings.Join(cfg.Run.BuildTags, ","))
|
||||
|
||||
h := sha256.New()
|
||||
if _, err := h.Write(configData.Bytes()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.Sum(nil), nil
|
||||
}
|
||||
|
||||
func (e *Executor) acquireFileLock() bool {
|
||||
if e.cfg.Run.AllowParallelRunners {
|
||||
e.debugf("Parallel runners are allowed, no locking")
|
||||
return true
|
||||
}
|
||||
|
||||
lockFile := filepath.Join(os.TempDir(), "golangci-lint.lock")
|
||||
e.debugf("Locking on file %s...", lockFile)
|
||||
f := flock.New(lockFile)
|
||||
const retryDelay = time.Second
|
||||
|
||||
ctx := context.Background()
|
||||
if !e.cfg.Run.AllowSerialRunners {
|
||||
const totalTimeout = 5 * time.Second
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, totalTimeout)
|
||||
defer cancel()
|
||||
}
|
||||
if ok, _ := f.TryLockContext(ctx, retryDelay); !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
e.flock = f
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *Executor) releaseFileLock() {
|
||||
if e.cfg.Run.AllowParallelRunners {
|
||||
return
|
||||
}
|
||||
|
||||
if err := e.flock.Unlock(); err != nil {
|
||||
e.debugf("Failed to unlock on file: %s", err)
|
||||
}
|
||||
if err := os.Remove(e.flock.Path()); err != nil {
|
||||
e.debugf("Failed to remove lock file: %s", err)
|
||||
}
|
||||
func wh(text string) string {
|
||||
return color.GreenString(text)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||
)
|
||||
|
||||
|
@ -21,16 +22,51 @@ func (e *Executor) initHelp() {
|
|||
return cmd.Help()
|
||||
},
|
||||
}
|
||||
e.rootCmd.SetHelpCommand(helpCmd)
|
||||
|
||||
lintersHelpCmd := &cobra.Command{
|
||||
helpCmd.AddCommand(&cobra.Command{
|
||||
Use: "linters",
|
||||
Short: "Help about linters",
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
Run: e.executeLintersHelp,
|
||||
Run: e.executeHelp,
|
||||
})
|
||||
|
||||
e.rootCmd.SetHelpCommand(helpCmd)
|
||||
}
|
||||
|
||||
func (e *Executor) executeHelp(_ *cobra.Command, _ []string) {
|
||||
var enabledLCs, disabledLCs []*linter.Config
|
||||
for _, lc := range e.dbManager.GetAllSupportedLinterConfigs() {
|
||||
if lc.Internal {
|
||||
continue
|
||||
}
|
||||
|
||||
if lc.EnabledByDefault {
|
||||
enabledLCs = append(enabledLCs, lc)
|
||||
} else {
|
||||
disabledLCs = append(disabledLCs, lc)
|
||||
}
|
||||
}
|
||||
|
||||
color.Green("Enabled by default linters:\n")
|
||||
printLinterConfigs(enabledLCs)
|
||||
color.Red("\nDisabled by default linters:\n")
|
||||
printLinterConfigs(disabledLCs)
|
||||
|
||||
color.Green("\nLinters presets:")
|
||||
for _, p := range lintersdb.AllPresets() {
|
||||
linters := e.dbManager.GetAllLinterConfigsForPreset(p)
|
||||
var linterNames []string
|
||||
for _, lc := range linters {
|
||||
if lc.Internal {
|
||||
continue
|
||||
}
|
||||
|
||||
linterNames = append(linterNames, lc.Name())
|
||||
}
|
||||
sort.Strings(linterNames)
|
||||
fmt.Fprintf(logutils.StdOut, "%s: %s\n", color.YellowString(p), strings.Join(linterNames, ", "))
|
||||
}
|
||||
helpCmd.AddCommand(lintersHelpCmd)
|
||||
}
|
||||
|
||||
func printLinterConfigs(lcs []*linter.Config) {
|
||||
|
@ -59,38 +95,3 @@ func printLinterConfigs(lcs []*linter.Config) {
|
|||
altNamesStr, deprecatedMark, linterDescription, !lc.IsSlowLinter(), lc.CanAutoFix)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) executeLintersHelp(_ *cobra.Command, _ []string) {
|
||||
var enabledLCs, disabledLCs []*linter.Config
|
||||
for _, lc := range e.DBManager.GetAllSupportedLinterConfigs() {
|
||||
if lc.Internal {
|
||||
continue
|
||||
}
|
||||
|
||||
if lc.EnabledByDefault {
|
||||
enabledLCs = append(enabledLCs, lc)
|
||||
} else {
|
||||
disabledLCs = append(disabledLCs, lc)
|
||||
}
|
||||
}
|
||||
|
||||
color.Green("Enabled by default linters:\n")
|
||||
printLinterConfigs(enabledLCs)
|
||||
color.Red("\nDisabled by default linters:\n")
|
||||
printLinterConfigs(disabledLCs)
|
||||
|
||||
color.Green("\nLinters presets:")
|
||||
for _, p := range e.DBManager.AllPresets() {
|
||||
linters := e.DBManager.GetAllLinterConfigsForPreset(p)
|
||||
var linterNames []string
|
||||
for _, lc := range linters {
|
||||
if lc.Internal {
|
||||
continue
|
||||
}
|
||||
|
||||
linterNames = append(linterNames, lc.Name())
|
||||
}
|
||||
sort.Strings(linterNames)
|
||||
fmt.Fprintf(logutils.StdOut, "%s: %s\n", color.YellowString(p), strings.Join(linterNames, ", "))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,10 +10,11 @@ import (
|
|||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||
)
|
||||
|
||||
func (e *Executor) initLinters() {
|
||||
e.lintersCmd = &cobra.Command{
|
||||
lintersCmd := &cobra.Command{
|
||||
Use: "linters",
|
||||
Short: "List current linters configuration",
|
||||
Args: cobra.NoArgs,
|
||||
|
@ -21,29 +22,20 @@ func (e *Executor) initLinters() {
|
|||
RunE: e.executeLinters,
|
||||
}
|
||||
|
||||
fs := e.lintersCmd.Flags()
|
||||
fs := lintersCmd.Flags()
|
||||
fs.SortFlags = false // sort them as they are defined here
|
||||
|
||||
initConfigFileFlagSet(fs, &e.cfg.Run)
|
||||
e.initLintersFlagSet(fs, &e.cfg.Linters)
|
||||
initLintersFlagSet(fs, &e.cfg.Linters)
|
||||
|
||||
e.rootCmd.AddCommand(e.lintersCmd)
|
||||
}
|
||||
e.rootCmd.AddCommand(lintersCmd)
|
||||
|
||||
func (e *Executor) initLintersFlagSet(fs *pflag.FlagSet, cfg *config.Linters) {
|
||||
fs.StringSliceVarP(&cfg.Disable, "disable", "D", nil, wh("Disable specific linter"))
|
||||
fs.BoolVar(&cfg.DisableAll, "disable-all", false, wh("Disable all linters"))
|
||||
fs.StringSliceVarP(&cfg.Enable, "enable", "E", nil, wh("Enable specific linter"))
|
||||
fs.BoolVar(&cfg.EnableAll, "enable-all", false, wh("Enable all linters"))
|
||||
fs.BoolVar(&cfg.Fast, "fast", false, wh("Enable only fast linters from enabled linters set (first run won't be fast)"))
|
||||
fs.StringSliceVarP(&cfg.Presets, "presets", "p", nil,
|
||||
wh(fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint help linters' to see "+
|
||||
"them. This option implies option --disable-all", strings.Join(e.DBManager.AllPresets(), "|"))))
|
||||
e.lintersCmd = lintersCmd
|
||||
}
|
||||
|
||||
// executeLinters runs the 'linters' CLI command, which displays the supported linters.
|
||||
func (e *Executor) executeLinters(_ *cobra.Command, _ []string) error {
|
||||
enabledLintersMap, err := e.EnabledLintersSet.GetEnabledLintersMap()
|
||||
enabledLintersMap, err := e.enabledLintersSet.GetEnabledLintersMap()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get enabled linters: %w", err)
|
||||
}
|
||||
|
@ -51,7 +43,7 @@ func (e *Executor) executeLinters(_ *cobra.Command, _ []string) error {
|
|||
var enabledLinters []*linter.Config
|
||||
var disabledLCs []*linter.Config
|
||||
|
||||
for _, lc := range e.DBManager.GetAllSupportedLinterConfigs() {
|
||||
for _, lc := range e.dbManager.GetAllSupportedLinterConfigs() {
|
||||
if lc.Internal {
|
||||
continue
|
||||
}
|
||||
|
@ -70,3 +62,14 @@ func (e *Executor) executeLinters(_ *cobra.Command, _ []string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initLintersFlagSet(fs *pflag.FlagSet, cfg *config.Linters) {
|
||||
fs.StringSliceVarP(&cfg.Disable, "disable", "D", nil, wh("Disable specific linter"))
|
||||
fs.BoolVar(&cfg.DisableAll, "disable-all", false, wh("Disable all linters"))
|
||||
fs.StringSliceVarP(&cfg.Enable, "enable", "E", nil, wh("Enable specific linter"))
|
||||
fs.BoolVar(&cfg.EnableAll, "enable-all", false, wh("Enable all linters"))
|
||||
fs.BoolVar(&cfg.Fast, "fast", false, wh("Enable only fast linters from enabled linters set (first run won't be fast)"))
|
||||
fs.StringSliceVarP(&cfg.Presets, "presets", "p", nil,
|
||||
wh(fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint help linters' to see "+
|
||||
"them. This option implies option --disable-all", strings.Join(lintersdb.AllPresets(), "|"))))
|
||||
}
|
||||
|
|
|
@ -22,6 +22,24 @@ const (
|
|||
envMemProfileRate = "GL_MEM_PROFILE_RATE"
|
||||
)
|
||||
|
||||
func (e *Executor) initRoot() {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "golangci-lint",
|
||||
Short: "golangci-lint is a smart linters runner.",
|
||||
Long: `Smart, fast linters runner.`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return cmd.Help()
|
||||
},
|
||||
PersistentPreRunE: e.persistentPreRun,
|
||||
PersistentPostRunE: e.persistentPostRun,
|
||||
}
|
||||
|
||||
initRootFlagSet(rootCmd.PersistentFlags(), e.cfg)
|
||||
|
||||
e.rootCmd = rootCmd
|
||||
}
|
||||
|
||||
func (e *Executor) persistentPreRun(_ *cobra.Command, _ []string) error {
|
||||
if e.cfg.Run.PrintVersion {
|
||||
_ = printVersion(logutils.StdOut, e.buildInfo)
|
||||
|
@ -75,7 +93,7 @@ func (e *Executor) persistentPostRun(_ *cobra.Command, _ []string) error {
|
|||
printMemStats(&ms, e.log)
|
||||
|
||||
if err := pprof.WriteHeapProfile(f); err != nil {
|
||||
return fmt.Errorf("cCan't write heap profile: %w", err)
|
||||
return fmt.Errorf("can't write heap profile: %w", err)
|
||||
}
|
||||
_ = f.Close()
|
||||
}
|
||||
|
@ -89,6 +107,20 @@ func (e *Executor) persistentPostRun(_ *cobra.Command, _ []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func initRootFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
|
||||
fs.BoolVarP(&cfg.Run.IsVerbose, "verbose", "v", false, wh("Verbose output"))
|
||||
fs.StringVar(&cfg.Output.Color, "color", "auto", wh("Use color when printing; can be 'always', 'auto', or 'never'"))
|
||||
|
||||
fs.StringVar(&cfg.Run.CPUProfilePath, "cpu-profile-path", "", wh("Path to CPU profile output file"))
|
||||
fs.StringVar(&cfg.Run.MemProfilePath, "mem-profile-path", "", wh("Path to memory profile output file"))
|
||||
fs.StringVar(&cfg.Run.TracePath, "trace-path", "", wh("Path to trace output file"))
|
||||
|
||||
fs.IntVarP(&cfg.Run.Concurrency, "concurrency", "j", getDefaultConcurrency(),
|
||||
wh("Number of CPUs to use (Default: number of logical CPUs)"))
|
||||
|
||||
fs.BoolVar(&cfg.Run.PrintVersion, "version", false, wh("Print version"))
|
||||
}
|
||||
|
||||
func printMemStats(ms *runtime.MemStats, logger logutils.Log) {
|
||||
logger.Infof("Mem stats: alloc=%s total_alloc=%s sys=%s "+
|
||||
"heap_alloc=%s heap_sys=%s heap_idle=%s heap_released=%s heap_in_use=%s "+
|
||||
|
@ -126,50 +158,3 @@ func getDefaultConcurrency() int {
|
|||
|
||||
return runtime.NumCPU()
|
||||
}
|
||||
|
||||
func (e *Executor) initRoot() {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "golangci-lint",
|
||||
Short: "golangci-lint is a smart linters runner.",
|
||||
Long: `Smart, fast linters runner.`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return cmd.Help()
|
||||
},
|
||||
PersistentPreRunE: e.persistentPreRun,
|
||||
PersistentPostRunE: e.persistentPostRun,
|
||||
}
|
||||
|
||||
initRootFlagSet(rootCmd.PersistentFlags(), e.cfg, e.needVersionOption())
|
||||
e.rootCmd = rootCmd
|
||||
}
|
||||
|
||||
func (e *Executor) needVersionOption() bool {
|
||||
return e.buildInfo.Date != ""
|
||||
}
|
||||
|
||||
func initRootFlagSet(fs *pflag.FlagSet, cfg *config.Config, needVersionOption bool) {
|
||||
fs.BoolVarP(&cfg.Run.IsVerbose, "verbose", "v", false, wh("Verbose output"))
|
||||
|
||||
var silent bool
|
||||
fs.BoolVarP(&silent, "silent", "s", false, wh("Disables congrats outputs"))
|
||||
if err := fs.MarkHidden("silent"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err := fs.MarkDeprecated("silent",
|
||||
"now golangci-lint by default is silent: it doesn't print Congrats message")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fs.StringVar(&cfg.Run.CPUProfilePath, "cpu-profile-path", "", wh("Path to CPU profile output file"))
|
||||
fs.StringVar(&cfg.Run.MemProfilePath, "mem-profile-path", "", wh("Path to memory profile output file"))
|
||||
fs.StringVar(&cfg.Run.TracePath, "trace-path", "", wh("Path to trace output file"))
|
||||
fs.IntVarP(&cfg.Run.Concurrency, "concurrency", "j", getDefaultConcurrency(),
|
||||
wh("Number of CPUs to use (Default: number of logical CPUs)"))
|
||||
if needVersionOption {
|
||||
fs.BoolVar(&cfg.Run.PrintVersion, "version", false, wh("Print version"))
|
||||
}
|
||||
|
||||
fs.StringVar(&cfg.Output.Color, "color", "auto", wh("Use color when printing; can be 'always', 'auto', or 'never'"))
|
||||
}
|
||||
|
|
|
@ -7,12 +7,14 @@ import (
|
|||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/gofrs/flock"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"golang.org/x/exp/maps"
|
||||
|
@ -38,212 +40,8 @@ const (
|
|||
envMemLogEvery = "GL_MEM_LOG_EVERY"
|
||||
)
|
||||
|
||||
//nolint:funlen,gomnd
|
||||
func (e *Executor) initFlagSet(fs *pflag.FlagSet, cfg *config.Config, isFinalInit bool) {
|
||||
hideFlag := func(name string) {
|
||||
if err := fs.MarkHidden(name); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// we run initFlagSet multiple times, but we wouldn't like to see deprecation message multiple times
|
||||
if isFinalInit {
|
||||
const deprecateMessage = "flag will be removed soon, please, use .golangci.yml config"
|
||||
if err := fs.MarkDeprecated(name, deprecateMessage); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Config file config
|
||||
rc := &cfg.Run
|
||||
initConfigFileFlagSet(fs, rc)
|
||||
|
||||
// Output config
|
||||
oc := &cfg.Output
|
||||
fs.StringVar(&oc.Format, "out-format",
|
||||
config.OutFormatColoredLineNumber,
|
||||
wh(fmt.Sprintf("Format of output: %s", strings.Join(config.OutFormats, "|"))))
|
||||
fs.BoolVar(&oc.PrintIssuedLine, "print-issued-lines", true, wh("Print lines of code with issue"))
|
||||
fs.BoolVar(&oc.PrintLinterName, "print-linter-name", true, wh("Print linter name in issue line"))
|
||||
fs.BoolVar(&oc.UniqByLine, "uniq-by-line", true, wh("Make issues output unique by line"))
|
||||
fs.BoolVar(&oc.SortResults, "sort-results", false, wh("Sort linter results"))
|
||||
fs.BoolVar(&oc.PrintWelcomeMessage, "print-welcome", false, wh("Print welcome message"))
|
||||
fs.StringVar(&oc.PathPrefix, "path-prefix", "", wh("Path prefix to add to output"))
|
||||
hideFlag("print-welcome") // no longer used
|
||||
|
||||
fs.BoolVar(&cfg.InternalCmdTest, "internal-cmd-test", false, wh("Option is used only for testing golangci-lint command, don't use it"))
|
||||
if err := fs.MarkHidden("internal-cmd-test"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Run config
|
||||
fs.StringVar(&rc.ModulesDownloadMode, "modules-download-mode", "",
|
||||
wh("Modules download mode. If not empty, passed as -mod=<mode> to go tools"))
|
||||
fs.IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code",
|
||||
exitcodes.IssuesFound, wh("Exit code when issues were found"))
|
||||
fs.StringVar(&rc.Go, "go", "", wh("Targeted Go version"))
|
||||
fs.StringSliceVar(&rc.BuildTags, "build-tags", nil, wh("Build tags"))
|
||||
|
||||
fs.DurationVar(&rc.Timeout, "deadline", defaultTimeout, wh("Deadline for total work"))
|
||||
if err := fs.MarkHidden("deadline"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fs.DurationVar(&rc.Timeout, "timeout", defaultTimeout, wh("Timeout for total work"))
|
||||
|
||||
fs.BoolVar(&rc.AnalyzeTests, "tests", true, wh("Analyze tests (*_test.go)"))
|
||||
fs.BoolVar(&rc.PrintResourcesUsage, "print-resources-usage", false,
|
||||
wh("Print avg and max memory usage of golangci-lint and total time"))
|
||||
fs.StringSliceVar(&rc.SkipDirs, "skip-dirs", nil, wh("Regexps of directories to skip"))
|
||||
fs.BoolVar(&rc.UseDefaultSkipDirs, "skip-dirs-use-default", true, getDefaultDirectoryExcludeHelp())
|
||||
fs.StringSliceVar(&rc.SkipFiles, "skip-files", nil, wh("Regexps of files to skip"))
|
||||
|
||||
const allowParallelDesc = "Allow multiple parallel golangci-lint instances running. " +
|
||||
"If false (default) - golangci-lint acquires file lock on start."
|
||||
fs.BoolVar(&rc.AllowParallelRunners, "allow-parallel-runners", false, wh(allowParallelDesc))
|
||||
const allowSerialDesc = "Allow multiple golangci-lint instances running, but serialize them around a lock. " +
|
||||
"If false (default) - golangci-lint exits with an error if it fails to acquire file lock on start."
|
||||
fs.BoolVar(&rc.AllowSerialRunners, "allow-serial-runners", false, wh(allowSerialDesc))
|
||||
fs.BoolVar(&rc.ShowStats, "show-stats", false, wh("Show statistics per linter"))
|
||||
|
||||
// Linters settings config
|
||||
lsc := &cfg.LintersSettings
|
||||
|
||||
// Hide all linters settings flags: they were initially visible,
|
||||
// but when number of linters started to grow it became obvious that
|
||||
// we can't fill 90% of flags by linters settings: common flags became hard to find.
|
||||
// New linters settings should be done only through config file.
|
||||
fs.BoolVar(&lsc.Errcheck.CheckTypeAssertions, "errcheck.check-type-assertions",
|
||||
false, "Errcheck: check for ignored type assertion results")
|
||||
hideFlag("errcheck.check-type-assertions")
|
||||
fs.BoolVar(&lsc.Errcheck.CheckAssignToBlank, "errcheck.check-blank", false,
|
||||
"Errcheck: check for errors assigned to blank identifier: _ = errFunc()")
|
||||
hideFlag("errcheck.check-blank")
|
||||
fs.StringVar(&lsc.Errcheck.Exclude, "errcheck.exclude", "",
|
||||
"Path to a file containing a list of functions to exclude from checking")
|
||||
hideFlag("errcheck.exclude")
|
||||
fs.StringVar(&lsc.Errcheck.Ignore, "errcheck.ignore", "fmt:.*",
|
||||
`Comma-separated list of pairs of the form pkg:regex. The regex is used to ignore names within pkg`)
|
||||
hideFlag("errcheck.ignore")
|
||||
|
||||
fs.BoolVar(&lsc.Govet.CheckShadowing, "govet.check-shadowing", false,
|
||||
"Govet: check for shadowed variables")
|
||||
hideFlag("govet.check-shadowing")
|
||||
|
||||
fs.Float64Var(&lsc.Golint.MinConfidence, "golint.min-confidence", 0.8,
|
||||
"Golint: minimum confidence of a problem to print it")
|
||||
hideFlag("golint.min-confidence")
|
||||
|
||||
fs.BoolVar(&lsc.Gofmt.Simplify, "gofmt.simplify", true, "Gofmt: simplify code")
|
||||
hideFlag("gofmt.simplify")
|
||||
|
||||
fs.IntVar(&lsc.Gocyclo.MinComplexity, "gocyclo.min-complexity",
|
||||
30, "Minimal complexity of function to report it")
|
||||
hideFlag("gocyclo.min-complexity")
|
||||
|
||||
fs.BoolVar(&lsc.Maligned.SuggestNewOrder, "maligned.suggest-new", false,
|
||||
"Maligned: print suggested more optimal struct fields ordering")
|
||||
hideFlag("maligned.suggest-new")
|
||||
|
||||
fs.IntVar(&lsc.Dupl.Threshold, "dupl.threshold",
|
||||
150, "Dupl: Minimal threshold to detect copy-paste")
|
||||
hideFlag("dupl.threshold")
|
||||
|
||||
fs.BoolVar(&lsc.Goconst.MatchWithConstants, "goconst.match-constant",
|
||||
true, "Goconst: look for existing constants matching the values")
|
||||
hideFlag("goconst.match-constant")
|
||||
fs.IntVar(&lsc.Goconst.MinStringLen, "goconst.min-len",
|
||||
3, "Goconst: minimum constant string length")
|
||||
hideFlag("goconst.min-len")
|
||||
fs.IntVar(&lsc.Goconst.MinOccurrencesCount, "goconst.min-occurrences",
|
||||
3, "Goconst: minimum occurrences of constant string count to trigger issue")
|
||||
hideFlag("goconst.min-occurrences")
|
||||
fs.BoolVar(&lsc.Goconst.ParseNumbers, "goconst.numbers",
|
||||
false, "Goconst: search also for duplicated numbers")
|
||||
hideFlag("goconst.numbers")
|
||||
fs.IntVar(&lsc.Goconst.NumberMin, "goconst.min",
|
||||
3, "minimum value, only works with goconst.numbers")
|
||||
hideFlag("goconst.min")
|
||||
fs.IntVar(&lsc.Goconst.NumberMax, "goconst.max",
|
||||
3, "maximum value, only works with goconst.numbers")
|
||||
hideFlag("goconst.max")
|
||||
fs.BoolVar(&lsc.Goconst.IgnoreCalls, "goconst.ignore-calls",
|
||||
true, "Goconst: ignore when constant is not used as function argument")
|
||||
hideFlag("goconst.ignore-calls")
|
||||
|
||||
fs.IntVar(&lsc.Lll.TabWidth, "lll.tab-width", 1,
|
||||
"Lll: tab width in spaces")
|
||||
hideFlag("lll.tab-width")
|
||||
|
||||
// Linters config
|
||||
lc := &cfg.Linters
|
||||
e.initLintersFlagSet(fs, lc)
|
||||
|
||||
// Issues config
|
||||
ic := &cfg.Issues
|
||||
fs.StringSliceVarP(&ic.ExcludePatterns, "exclude", "e", nil, wh("Exclude issue by regexp"))
|
||||
fs.BoolVar(&ic.UseDefaultExcludes, "exclude-use-default", true, getDefaultIssueExcludeHelp())
|
||||
fs.BoolVar(&ic.ExcludeCaseSensitive, "exclude-case-sensitive", false, wh("If set to true exclude "+
|
||||
"and exclude rules regular expressions are case sensitive"))
|
||||
|
||||
fs.IntVar(&ic.MaxIssuesPerLinter, "max-issues-per-linter", 50,
|
||||
wh("Maximum issues count per one linter. Set to 0 to disable"))
|
||||
fs.IntVar(&ic.MaxSameIssues, "max-same-issues", 3,
|
||||
wh("Maximum count of issues with the same text. Set to 0 to disable"))
|
||||
|
||||
fs.BoolVarP(&ic.Diff, "new", "n", false,
|
||||
wh("Show only new issues: if there are unstaged changes or untracked files, only those changes "+
|
||||
"are analyzed, else only changes in HEAD~ are analyzed.\nIt's a super-useful option for integration "+
|
||||
"of golangci-lint into existing large codebase.\nIt's not practical to fix all existing issues at "+
|
||||
"the moment of integration: much better to not allow issues in new code.\nFor CI setups, prefer "+
|
||||
"--new-from-rev=HEAD~, as --new can skip linting the current patch if any scripts generate "+
|
||||
"unstaged files before golangci-lint runs."))
|
||||
fs.StringVar(&ic.DiffFromRevision, "new-from-rev", "",
|
||||
wh("Show only new issues created after git revision `REV`"))
|
||||
fs.StringVar(&ic.DiffPatchFilePath, "new-from-patch", "",
|
||||
wh("Show only new issues created in git patch with file path `PATH`"))
|
||||
fs.BoolVar(&ic.WholeFiles, "whole-files", false,
|
||||
wh("Show issues in any part of update files (requires new-from-rev or new-from-patch)"))
|
||||
fs.BoolVar(&ic.NeedFix, "fix", false, wh("Fix found issues (if it's supported by the linter)"))
|
||||
}
|
||||
|
||||
func (e *Executor) initRunConfiguration(cmd *cobra.Command) {
|
||||
fs := cmd.Flags()
|
||||
fs.SortFlags = false // sort them as they are defined here
|
||||
e.initFlagSet(fs, e.cfg, true)
|
||||
}
|
||||
|
||||
func (e *Executor) getConfigForCommandLine() (*config.Config, error) {
|
||||
// We use another pflag.FlagSet here to not set `changed` flag
|
||||
// on cmd.Flags() options. Otherwise, string slice options will be duplicated.
|
||||
fs := pflag.NewFlagSet("config flag set", pflag.ContinueOnError)
|
||||
|
||||
var cfg config.Config
|
||||
// Don't do `fs.AddFlagSet(cmd.Flags())` because it shares flags representations:
|
||||
// `changed` variable inside string slice vars will be shared.
|
||||
// Use another config variable here, not e.cfg, to not
|
||||
// affect main parsing by this parsing of only config option.
|
||||
e.initFlagSet(fs, &cfg, false)
|
||||
initVersionFlagSet(fs, &cfg)
|
||||
|
||||
// Parse max options, even force version option: don't want
|
||||
// to get access to Executor here: it's error-prone to use
|
||||
// cfg vs e.cfg.
|
||||
initRootFlagSet(fs, &cfg, true)
|
||||
|
||||
fs.Usage = func() {} // otherwise, help text will be printed twice
|
||||
if err := fs.Parse(os.Args); err != nil {
|
||||
if errors.Is(err, pflag.ErrHelp) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("can't parse args: %w", err)
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func (e *Executor) initRun() {
|
||||
e.runCmd = &cobra.Command{
|
||||
runCmd := &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "Run the linters",
|
||||
Run: e.executeRun,
|
||||
|
@ -257,94 +55,50 @@ func (e *Executor) initRun() {
|
|||
e.releaseFileLock()
|
||||
},
|
||||
}
|
||||
e.rootCmd.AddCommand(e.runCmd)
|
||||
|
||||
e.runCmd.SetOut(logutils.StdOut) // use custom output to properly color it in Windows terminals
|
||||
e.runCmd.SetErr(logutils.StdErr)
|
||||
runCmd.SetOut(logutils.StdOut) // use custom output to properly color it in Windows terminals
|
||||
runCmd.SetErr(logutils.StdErr)
|
||||
|
||||
e.initRunConfiguration(e.runCmd)
|
||||
fs := runCmd.Flags()
|
||||
fs.SortFlags = false // sort them as they are defined here
|
||||
|
||||
initRunFlagSet(fs, e.cfg)
|
||||
|
||||
e.rootCmd.AddCommand(runCmd)
|
||||
|
||||
e.runCmd = runCmd
|
||||
}
|
||||
|
||||
func fixSlicesFlags(fs *pflag.FlagSet) {
|
||||
// It's a dirty hack to set flag.Changed to true for every string slice flag.
|
||||
// It's necessary to merge config and command-line slices: otherwise command-line
|
||||
// flags will always overwrite ones from the config.
|
||||
fs.VisitAll(func(f *pflag.Flag) {
|
||||
if f.Value.Type() != "stringSlice" {
|
||||
return
|
||||
// executeRun executes the 'run' CLI command, which runs the linters.
|
||||
func (e *Executor) executeRun(_ *cobra.Command, args []string) {
|
||||
needTrackResources := e.cfg.Run.IsVerbose || e.cfg.Run.PrintResourcesUsage
|
||||
trackResourcesEndCh := make(chan struct{})
|
||||
defer func() { // XXX: this defer must be before ctx.cancel defer
|
||||
if needTrackResources { // wait until resource tracking finished to print properly
|
||||
<-trackResourcesEndCh
|
||||
}
|
||||
}()
|
||||
|
||||
s, err := fs.GetStringSlice(f.Name)
|
||||
if err != nil {
|
||||
return
|
||||
ctx, cancel := context.WithTimeout(context.Background(), e.cfg.Run.Timeout)
|
||||
defer cancel()
|
||||
|
||||
if needTrackResources {
|
||||
go watchResources(ctx, trackResourcesEndCh, e.log, e.debugf)
|
||||
}
|
||||
|
||||
if err := e.runAndPrint(ctx, args); err != nil {
|
||||
e.log.Errorf("Running error: %s", err)
|
||||
if e.exitCode == exitcodes.Success {
|
||||
var exitErr *exitcodes.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
e.exitCode = exitErr.Code
|
||||
} else {
|
||||
e.exitCode = exitcodes.Failure
|
||||
}
|
||||
}
|
||||
|
||||
if s == nil { // assume that every string slice flag has nil as the default
|
||||
return
|
||||
}
|
||||
|
||||
var safe []string
|
||||
for _, v := range s {
|
||||
// add quotes to escape comma because spf13/pflag use a CSV parser:
|
||||
// https://github.com/spf13/pflag/blob/85dd5c8bc61cfa382fecd072378089d4e856579d/string_slice.go#L43
|
||||
safe = append(safe, `"`+v+`"`)
|
||||
}
|
||||
|
||||
// calling Set sets Changed to true: next Set calls will append, not overwrite
|
||||
_ = f.Value.Set(strings.Join(safe, ","))
|
||||
})
|
||||
}
|
||||
|
||||
// runAnalysis executes the linters that have been enabled in the configuration.
|
||||
func (e *Executor) runAnalysis(ctx context.Context, args []string) ([]result.Issue, error) {
|
||||
e.cfg.Run.Args = args
|
||||
|
||||
lintersToRun, err := e.EnabledLintersSet.GetOptimizedLinters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enabledLintersMap, err := e.EnabledLintersSet.GetEnabledLintersMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, lc := range e.DBManager.GetAllSupportedLinterConfigs() {
|
||||
isEnabled := enabledLintersMap[lc.Name()] != nil
|
||||
e.reportData.AddLinter(lc.Name(), isEnabled, lc.EnabledByDefault)
|
||||
}
|
||||
|
||||
lintCtx, err := e.contextLoader.Load(ctx, lintersToRun)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("context loading failed: %w", err)
|
||||
}
|
||||
lintCtx.Log = e.log.Child(logutils.DebugKeyLintersContext)
|
||||
|
||||
runner, err := lint.NewRunner(e.cfg, e.log.Child(logutils.DebugKeyRunner),
|
||||
e.goenv, e.EnabledLintersSet, e.lineCache, e.fileCache, e.DBManager, lintCtx.Packages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return runner.Run(ctx, lintersToRun, lintCtx)
|
||||
}
|
||||
|
||||
func (e *Executor) setOutputToDevNull() (savedStdout, savedStderr *os.File) {
|
||||
savedStdout, savedStderr = os.Stdout, os.Stderr
|
||||
devNull, err := os.Open(os.DevNull)
|
||||
if err != nil {
|
||||
e.log.Warnf("Can't open null device %q: %s", os.DevNull, err)
|
||||
return
|
||||
}
|
||||
|
||||
os.Stdout, os.Stderr = devNull, devNull
|
||||
return
|
||||
}
|
||||
|
||||
func (e *Executor) setExitCodeIfIssuesFound(issues []result.Issue) {
|
||||
if len(issues) != 0 {
|
||||
e.exitCode = e.cfg.Run.ExitCodeIfIssuesFound
|
||||
}
|
||||
e.setupExitCode(ctx)
|
||||
}
|
||||
|
||||
func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
|
||||
|
@ -388,6 +142,58 @@ func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// runAnalysis executes the linters that have been enabled in the configuration.
|
||||
func (e *Executor) runAnalysis(ctx context.Context, args []string) ([]result.Issue, error) {
|
||||
e.cfg.Run.Args = args
|
||||
|
||||
lintersToRun, err := e.enabledLintersSet.GetOptimizedLinters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enabledLintersMap, err := e.enabledLintersSet.GetEnabledLintersMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, lc := range e.dbManager.GetAllSupportedLinterConfigs() {
|
||||
isEnabled := enabledLintersMap[lc.Name()] != nil
|
||||
e.reportData.AddLinter(lc.Name(), isEnabled, lc.EnabledByDefault)
|
||||
}
|
||||
|
||||
lintCtx, err := e.contextLoader.Load(ctx, lintersToRun)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("context loading failed: %w", err)
|
||||
}
|
||||
lintCtx.Log = e.log.Child(logutils.DebugKeyLintersContext)
|
||||
|
||||
runner, err := lint.NewRunner(e.cfg, e.log.Child(logutils.DebugKeyRunner),
|
||||
e.goenv, e.enabledLintersSet, e.lineCache, e.fileCache, e.dbManager, lintCtx.Packages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return runner.Run(ctx, lintersToRun, lintCtx)
|
||||
}
|
||||
|
||||
func (e *Executor) setOutputToDevNull() (savedStdout, savedStderr *os.File) {
|
||||
savedStdout, savedStderr = os.Stdout, os.Stderr
|
||||
devNull, err := os.Open(os.DevNull)
|
||||
if err != nil {
|
||||
e.log.Warnf("Can't open null device %q: %s", os.DevNull, err)
|
||||
return
|
||||
}
|
||||
|
||||
os.Stdout, os.Stderr = devNull, devNull
|
||||
return
|
||||
}
|
||||
|
||||
func (e *Executor) setExitCodeIfIssuesFound(issues []result.Issue) {
|
||||
if len(issues) != 0 {
|
||||
e.exitCode = e.cfg.Run.ExitCodeIfIssuesFound
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) printReports(issues []result.Issue, path, format string) error {
|
||||
w, shouldClose, err := e.createWriter(path)
|
||||
if err != nil {
|
||||
|
@ -487,47 +293,6 @@ func (e *Executor) printStats(issues []result.Issue) {
|
|||
}
|
||||
}
|
||||
|
||||
// executeRun executes the 'run' CLI command, which runs the linters.
|
||||
func (e *Executor) executeRun(_ *cobra.Command, args []string) {
|
||||
needTrackResources := e.cfg.Run.IsVerbose || e.cfg.Run.PrintResourcesUsage
|
||||
trackResourcesEndCh := make(chan struct{})
|
||||
defer func() { // XXX: this defer must be before ctx.cancel defer
|
||||
if needTrackResources { // wait until resource tracking finished to print properly
|
||||
<-trackResourcesEndCh
|
||||
}
|
||||
}()
|
||||
|
||||
e.setTimeoutToDeadlineIfOnlyDeadlineIsSet()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), e.cfg.Run.Timeout)
|
||||
defer cancel()
|
||||
|
||||
if needTrackResources {
|
||||
go watchResources(ctx, trackResourcesEndCh, e.log, e.debugf)
|
||||
}
|
||||
|
||||
if err := e.runAndPrint(ctx, args); err != nil {
|
||||
e.log.Errorf("Running error: %s", err)
|
||||
if e.exitCode == exitcodes.Success {
|
||||
var exitErr *exitcodes.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
e.exitCode = exitErr.Code
|
||||
} else {
|
||||
e.exitCode = exitcodes.Failure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
e.setupExitCode(ctx)
|
||||
}
|
||||
|
||||
// to be removed when deadline is finally decommissioned
|
||||
func (e *Executor) setTimeoutToDeadlineIfOnlyDeadlineIsSet() {
|
||||
deadlineValue := e.cfg.Run.Deadline
|
||||
if deadlineValue != 0 && e.cfg.Run.Timeout == defaultTimeout {
|
||||
e.cfg.Run.Timeout = deadlineValue
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) setupExitCode(ctx context.Context) {
|
||||
if ctx.Err() != nil {
|
||||
e.exitCode = exitcodes.Timeout
|
||||
|
@ -552,6 +317,130 @@ func (e *Executor) setupExitCode(ctx context.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func (e *Executor) acquireFileLock() bool {
|
||||
if e.cfg.Run.AllowParallelRunners {
|
||||
e.debugf("Parallel runners are allowed, no locking")
|
||||
return true
|
||||
}
|
||||
|
||||
lockFile := filepath.Join(os.TempDir(), "golangci-lint.lock")
|
||||
e.debugf("Locking on file %s...", lockFile)
|
||||
f := flock.New(lockFile)
|
||||
const retryDelay = time.Second
|
||||
|
||||
ctx := context.Background()
|
||||
if !e.cfg.Run.AllowSerialRunners {
|
||||
const totalTimeout = 5 * time.Second
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, totalTimeout)
|
||||
defer cancel()
|
||||
}
|
||||
if ok, _ := f.TryLockContext(ctx, retryDelay); !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
e.flock = f
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *Executor) releaseFileLock() {
|
||||
if e.cfg.Run.AllowParallelRunners {
|
||||
return
|
||||
}
|
||||
|
||||
if err := e.flock.Unlock(); err != nil {
|
||||
e.debugf("Failed to unlock on file: %s", err)
|
||||
}
|
||||
if err := os.Remove(e.flock.Path()); err != nil {
|
||||
e.debugf("Failed to remove lock file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gomnd
|
||||
func initRunFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
|
||||
fs.BoolVar(&cfg.InternalCmdTest, "internal-cmd-test", false, wh("Option is used only for testing golangci-lint command, don't use it"))
|
||||
if err := fs.MarkHidden("internal-cmd-test"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// --- Output config
|
||||
|
||||
oc := &cfg.Output
|
||||
fs.StringVar(&oc.Format, "out-format",
|
||||
config.OutFormatColoredLineNumber,
|
||||
wh(fmt.Sprintf("Format of output: %s", strings.Join(config.OutFormats, "|"))))
|
||||
fs.BoolVar(&oc.PrintIssuedLine, "print-issued-lines", true, wh("Print lines of code with issue"))
|
||||
fs.BoolVar(&oc.PrintLinterName, "print-linter-name", true, wh("Print linter name in issue line"))
|
||||
fs.BoolVar(&oc.UniqByLine, "uniq-by-line", true, wh("Make issues output unique by line"))
|
||||
fs.BoolVar(&oc.SortResults, "sort-results", false, wh("Sort linter results"))
|
||||
fs.BoolVar(&oc.PrintWelcomeMessage, "print-welcome", false, wh("Print welcome message"))
|
||||
fs.StringVar(&oc.PathPrefix, "path-prefix", "", wh("Path prefix to add to output"))
|
||||
|
||||
// --- Run config
|
||||
|
||||
rc := &cfg.Run
|
||||
|
||||
// Config file config
|
||||
initConfigFileFlagSet(fs, rc)
|
||||
|
||||
fs.StringVar(&rc.ModulesDownloadMode, "modules-download-mode", "",
|
||||
wh("Modules download mode. If not empty, passed as -mod=<mode> to go tools"))
|
||||
fs.IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code",
|
||||
exitcodes.IssuesFound, wh("Exit code when issues were found"))
|
||||
fs.StringVar(&rc.Go, "go", "", wh("Targeted Go version"))
|
||||
fs.StringSliceVar(&rc.BuildTags, "build-tags", nil, wh("Build tags"))
|
||||
|
||||
fs.DurationVar(&rc.Timeout, "timeout", defaultTimeout, wh("Timeout for total work"))
|
||||
|
||||
fs.BoolVar(&rc.AnalyzeTests, "tests", true, wh("Analyze tests (*_test.go)"))
|
||||
fs.BoolVar(&rc.PrintResourcesUsage, "print-resources-usage", false,
|
||||
wh("Print avg and max memory usage of golangci-lint and total time"))
|
||||
fs.StringSliceVar(&rc.SkipDirs, "skip-dirs", nil, wh("Regexps of directories to skip"))
|
||||
fs.BoolVar(&rc.UseDefaultSkipDirs, "skip-dirs-use-default", true, getDefaultDirectoryExcludeHelp())
|
||||
fs.StringSliceVar(&rc.SkipFiles, "skip-files", nil, wh("Regexps of files to skip"))
|
||||
|
||||
const allowParallelDesc = "Allow multiple parallel golangci-lint instances running. " +
|
||||
"If false (default) - golangci-lint acquires file lock on start."
|
||||
fs.BoolVar(&rc.AllowParallelRunners, "allow-parallel-runners", false, wh(allowParallelDesc))
|
||||
const allowSerialDesc = "Allow multiple golangci-lint instances running, but serialize them around a lock. " +
|
||||
"If false (default) - golangci-lint exits with an error if it fails to acquire file lock on start."
|
||||
fs.BoolVar(&rc.AllowSerialRunners, "allow-serial-runners", false, wh(allowSerialDesc))
|
||||
fs.BoolVar(&rc.ShowStats, "show-stats", false, wh("Show statistics per linter"))
|
||||
|
||||
// --- Linters config
|
||||
|
||||
lc := &cfg.Linters
|
||||
initLintersFlagSet(fs, lc)
|
||||
|
||||
// --- Issues config
|
||||
|
||||
ic := &cfg.Issues
|
||||
fs.StringSliceVarP(&ic.ExcludePatterns, "exclude", "e", nil, wh("Exclude issue by regexp"))
|
||||
fs.BoolVar(&ic.UseDefaultExcludes, "exclude-use-default", true, getDefaultIssueExcludeHelp())
|
||||
fs.BoolVar(&ic.ExcludeCaseSensitive, "exclude-case-sensitive", false, wh("If set to true exclude "+
|
||||
"and exclude rules regular expressions are case sensitive"))
|
||||
|
||||
fs.IntVar(&ic.MaxIssuesPerLinter, "max-issues-per-linter", 50,
|
||||
wh("Maximum issues count per one linter. Set to 0 to disable"))
|
||||
fs.IntVar(&ic.MaxSameIssues, "max-same-issues", 3,
|
||||
wh("Maximum count of issues with the same text. Set to 0 to disable"))
|
||||
|
||||
fs.BoolVarP(&ic.Diff, "new", "n", false,
|
||||
wh("Show only new issues: if there are unstaged changes or untracked files, only those changes "+
|
||||
"are analyzed, else only changes in HEAD~ are analyzed.\nIt's a super-useful option for integration "+
|
||||
"of golangci-lint into existing large codebase.\nIt's not practical to fix all existing issues at "+
|
||||
"the moment of integration: much better to not allow issues in new code.\nFor CI setups, prefer "+
|
||||
"--new-from-rev=HEAD~, as --new can skip linting the current patch if any scripts generate "+
|
||||
"unstaged files before golangci-lint runs."))
|
||||
fs.StringVar(&ic.DiffFromRevision, "new-from-rev", "",
|
||||
wh("Show only new issues created after git revision `REV`"))
|
||||
fs.StringVar(&ic.DiffPatchFilePath, "new-from-patch", "",
|
||||
wh("Show only new issues created in git patch with file path `PATH`"))
|
||||
fs.BoolVar(&ic.WholeFiles, "whole-files", false,
|
||||
wh("Show issues in any part of update files (requires new-from-rev or new-from-patch)"))
|
||||
fs.BoolVar(&ic.NeedFix, "fix", false, wh("Fix found issues (if it's supported by the linter)"))
|
||||
}
|
||||
|
||||
func watchResources(ctx context.Context, done chan struct{}, logger logutils.Log, debugf logutils.DebugFunc) {
|
||||
startedAt := time.Now()
|
||||
debugf("Started tracking time")
|
||||
|
@ -628,7 +517,3 @@ func getDefaultDirectoryExcludeHelp() string {
|
|||
parts = append(parts, "")
|
||||
return strings.Join(parts, "\n")
|
||||
}
|
||||
|
||||
func wh(text string) string {
|
||||
return color.GreenString(text)
|
||||
}
|
||||
|
|
|
@ -14,15 +14,66 @@ import (
|
|||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
)
|
||||
|
||||
type BuildInfo struct {
|
||||
GoVersion string `json:"goVersion"`
|
||||
Version string `json:"version"`
|
||||
Commit string `json:"commit"`
|
||||
Date string `json:"date"`
|
||||
}
|
||||
|
||||
type versionInfo struct {
|
||||
Info BuildInfo
|
||||
BuildInfo *debug.BuildInfo
|
||||
}
|
||||
|
||||
func (e *Executor) initVersionConfiguration(cmd *cobra.Command) {
|
||||
fs := cmd.Flags()
|
||||
func (e *Executor) initVersion() {
|
||||
versionCmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Version",
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
RunE: e.executeVersion,
|
||||
}
|
||||
|
||||
fs := versionCmd.Flags()
|
||||
fs.SortFlags = false // sort them as they are defined here
|
||||
|
||||
initVersionFlagSet(fs, e.cfg)
|
||||
|
||||
e.rootCmd.AddCommand(versionCmd)
|
||||
}
|
||||
|
||||
func (e *Executor) executeVersion(_ *cobra.Command, _ []string) error {
|
||||
if e.cfg.Version.Debug {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch strings.ToLower(e.cfg.Version.Format) {
|
||||
case "json":
|
||||
return json.NewEncoder(os.Stdout).Encode(versionInfo{
|
||||
Info: e.buildInfo,
|
||||
BuildInfo: info,
|
||||
})
|
||||
|
||||
default:
|
||||
fmt.Println(info.String())
|
||||
return printVersion(os.Stdout, e.buildInfo)
|
||||
}
|
||||
}
|
||||
|
||||
switch strings.ToLower(e.cfg.Version.Format) {
|
||||
case "short":
|
||||
fmt.Println(e.buildInfo.Version)
|
||||
return nil
|
||||
|
||||
case "json":
|
||||
return json.NewEncoder(os.Stdout).Encode(e.buildInfo)
|
||||
|
||||
default:
|
||||
return printVersion(os.Stdout, e.buildInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func initVersionFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
|
||||
|
@ -32,50 +83,6 @@ func initVersionFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
|
|||
fs.BoolVar(&vc.Debug, "debug", false, wh("Add build information"))
|
||||
}
|
||||
|
||||
func (e *Executor) initVersion() {
|
||||
versionCmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Version",
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
if e.cfg.Version.Debug {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch strings.ToLower(e.cfg.Version.Format) {
|
||||
case "json":
|
||||
return json.NewEncoder(os.Stdout).Encode(versionInfo{
|
||||
Info: e.buildInfo,
|
||||
BuildInfo: info,
|
||||
})
|
||||
|
||||
default:
|
||||
fmt.Println(info.String())
|
||||
return printVersion(os.Stdout, e.buildInfo)
|
||||
}
|
||||
}
|
||||
|
||||
switch strings.ToLower(e.cfg.Version.Format) {
|
||||
case "short":
|
||||
fmt.Println(e.buildInfo.Version)
|
||||
return nil
|
||||
|
||||
case "json":
|
||||
return json.NewEncoder(os.Stdout).Encode(e.buildInfo)
|
||||
|
||||
default:
|
||||
return printVersion(os.Stdout, e.buildInfo)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
e.rootCmd.AddCommand(versionCmd)
|
||||
e.initVersionConfiguration(versionCmd)
|
||||
}
|
||||
|
||||
func printVersion(w io.Writer, buildInfo BuildInfo) error {
|
||||
_, err := fmt.Fprintf(w, "golangci-lint has version %s built with %s from %s on %s\n",
|
||||
buildInfo.Version, buildInfo.GoVersion, buildInfo.Commit, buildInfo.Date)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue