mirror of
https://github.com/scratchfoundation/golangci-lint.git
synced 2025-08-13 14:58:45 -04:00
dev: new commands system (#4412)
This commit is contained in:
parent
b5d7302867
commit
784264d72e
20 changed files with 1189 additions and 1153 deletions
|
@ -1,24 +1,24 @@
|
|||
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"
|
||||
)
|
||||
|
||||
func (e *Executor) initCache() {
|
||||
type cacheCommand struct {
|
||||
cmd *cobra.Command
|
||||
}
|
||||
|
||||
func newCacheCommand() *cacheCommand {
|
||||
c := &cacheCommand{}
|
||||
|
||||
cacheCmd := &cobra.Command{
|
||||
Use: "cache",
|
||||
Short: "Cache control and information",
|
||||
|
@ -28,28 +28,31 @@ func (e *Executor) initCache() {
|
|||
},
|
||||
}
|
||||
|
||||
cacheCmd.AddCommand(&cobra.Command{
|
||||
Use: "clean",
|
||||
Short: "Clean cache",
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
RunE: e.executeCacheClean,
|
||||
})
|
||||
cacheCmd.AddCommand(&cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Show cache status",
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
Run: e.executeCacheStatus,
|
||||
})
|
||||
cacheCmd.AddCommand(
|
||||
&cobra.Command{
|
||||
Use: "clean",
|
||||
Short: "Clean cache",
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
RunE: c.executeClean,
|
||||
},
|
||||
&cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Show cache status",
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
Run: c.executeStatus,
|
||||
},
|
||||
)
|
||||
|
||||
// TODO: add trim command?
|
||||
c.cmd = cacheCmd
|
||||
|
||||
e.rootCmd.AddCommand(cacheCmd)
|
||||
return c
|
||||
}
|
||||
|
||||
func (e *Executor) executeCacheClean(_ *cobra.Command, _ []string) error {
|
||||
func (c *cacheCommand) executeClean(_ *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)
|
||||
}
|
||||
|
@ -57,13 +60,13 @@ func (e *Executor) executeCacheClean(_ *cobra.Command, _ []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) executeCacheStatus(_ *cobra.Command, _ []string) {
|
||||
func (c *cacheCommand) executeStatus(_ *cobra.Command, _ []string) {
|
||||
cacheDir := cache.DefaultDir()
|
||||
fmt.Fprintf(logutils.StdOut, "Dir: %s\n", cacheDir)
|
||||
_, _ = fmt.Fprintf(logutils.StdOut, "Dir: %s\n", cacheDir)
|
||||
|
||||
cacheSizeBytes, err := dirSizeBytes(cacheDir)
|
||||
if err == nil {
|
||||
fmt.Fprintf(logutils.StdOut, "Size: %s\n", fsutils.PrettifyBytesCount(cacheSizeBytes))
|
||||
_, _ = fmt.Fprintf(logutils.StdOut, "Size: %s\n", fsutils.PrettifyBytesCount(cacheSizeBytes))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,68 +80,3 @@ 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
|
||||
}
|
||||
|
|
|
@ -5,15 +5,27 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/exitcodes"
|
||||
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||
)
|
||||
|
||||
func (e *Executor) initConfig() {
|
||||
type configCommand struct {
|
||||
viper *viper.Viper
|
||||
cmd *cobra.Command
|
||||
|
||||
log logutils.Log
|
||||
}
|
||||
|
||||
func newConfigCommand(log logutils.Log) *configCommand {
|
||||
c := &configCommand{
|
||||
viper: viper.New(),
|
||||
log: log,
|
||||
}
|
||||
|
||||
configCmd := &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Config file information",
|
||||
|
@ -23,25 +35,38 @@ func (e *Executor) initConfig() {
|
|||
},
|
||||
}
|
||||
|
||||
pathCmd := &cobra.Command{
|
||||
Use: "path",
|
||||
Short: "Print used config path",
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
Run: e.executePath,
|
||||
}
|
||||
configCmd.AddCommand(
|
||||
&cobra.Command{
|
||||
Use: "path",
|
||||
Short: "Print used config path",
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
Run: c.execute,
|
||||
PreRunE: c.preRunE,
|
||||
},
|
||||
)
|
||||
|
||||
fs := pathCmd.Flags()
|
||||
fs.SortFlags = false // sort them as they are defined here
|
||||
c.cmd = configCmd
|
||||
|
||||
configCmd.AddCommand(pathCmd)
|
||||
e.rootCmd.AddCommand(configCmd)
|
||||
return c
|
||||
}
|
||||
|
||||
func (e *Executor) executePath(_ *cobra.Command, _ []string) {
|
||||
usedConfigFile := e.getUsedConfig()
|
||||
func (c *configCommand) preRunE(cmd *cobra.Command, _ []string) error {
|
||||
// The command doesn't depend on the real configuration.
|
||||
// It only needs to know the path of the configuration file.
|
||||
loader := config.NewLoader(c.log.Child(logutils.DebugKeyConfigReader), c.viper, cmd.Flags(), config.LoaderOptions{}, config.NewDefault())
|
||||
|
||||
if err := loader.Load(); err != nil {
|
||||
return fmt.Errorf("can't load config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *configCommand) execute(_ *cobra.Command, _ []string) {
|
||||
usedConfigFile := c.getUsedConfig()
|
||||
if usedConfigFile == "" {
|
||||
e.log.Warnf("No config file detected")
|
||||
c.log.Warnf("No config file detected")
|
||||
os.Exit(exitcodes.NoConfigFileDetected)
|
||||
}
|
||||
|
||||
|
@ -50,24 +75,17 @@ func (e *Executor) executePath(_ *cobra.Command, _ []string) {
|
|||
|
||||
// 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()
|
||||
func (c *configCommand) getUsedConfig() string {
|
||||
usedConfigFile := c.viper.ConfigFileUsed()
|
||||
if usedConfigFile == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
prettyUsedConfigFile, err := fsutils.ShortestRelPath(usedConfigFile, "")
|
||||
if err != nil {
|
||||
e.log.Warnf("Can't pretty print config file path: %s", err)
|
||||
c.log.Warnf("Can't pretty print config file path: %s", err)
|
||||
return usedConfigFile
|
||||
}
|
||||
|
||||
return prettyUsedConfigFile
|
||||
}
|
||||
|
||||
// --- 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`"))
|
||||
fs.BoolVar(&cfg.NoConfig, "no-config", false, wh("Don't read config file"))
|
||||
}
|
||||
|
|
|
@ -1,230 +0,0 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/gofrs/flock"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/golangci/golangci-lint/internal/pkgcache"
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis/load"
|
||||
"github.com/golangci/golangci-lint/pkg/goutil"
|
||||
"github.com/golangci/golangci-lint/pkg/lint"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||
"github.com/golangci/golangci-lint/pkg/report"
|
||||
"github.com/golangci/golangci-lint/pkg/timeutils"
|
||||
)
|
||||
|
||||
type Executor struct {
|
||||
rootCmd *cobra.Command
|
||||
|
||||
runCmd *cobra.Command // used by fixSlicesFlags, printStats
|
||||
lintersCmd *cobra.Command // used by fixSlicesFlags
|
||||
|
||||
exitCode int
|
||||
|
||||
buildInfo BuildInfo
|
||||
|
||||
cfg *config.Config // cfg is the unmarshaled data from the golangci config file.
|
||||
|
||||
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 {
|
||||
e := &Executor{
|
||||
cfg: config.NewDefault(),
|
||||
buildInfo: buildInfo,
|
||||
debugf: logutils.Debug(logutils.DebugKeyExec),
|
||||
}
|
||||
|
||||
e.log = report.NewLogWrapper(logutils.NewStderrLog(logutils.DebugKeyEmpty), &e.reportData)
|
||||
|
||||
// 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)
|
||||
}
|
||||
if commandLineCfg != nil {
|
||||
logutils.SetupVerboseLog(e.log, commandLineCfg.Run.IsVerbose)
|
||||
|
||||
switch commandLineCfg.Output.Color {
|
||||
case "always":
|
||||
color.NoColor = false
|
||||
case "never":
|
||||
color.NoColor = true
|
||||
case "auto":
|
||||
// nothing
|
||||
default:
|
||||
e.log.Fatalf("invalid value %q for --color; must be 'always', 'auto', or 'never'", commandLineCfg.Output.Color)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
e.log.Fatalf("Can't read config: %s", err)
|
||||
}
|
||||
|
||||
if commandLineCfg != nil && commandLineCfg.Run.Go != "" {
|
||||
// This hack allow to have the right Run information at least for the Go version (because the default value of the "go" flag is empty).
|
||||
// If you put a log for `m.cfg.Run.Go` inside `GetAllSupportedLinterConfigs`,
|
||||
// you will observe that at end (without this hack) the value will have the right value but too late,
|
||||
// the linters are already running with the previous uncompleted configuration.
|
||||
// TODO(ldez) there is a major problem with the executor:
|
||||
// the parsing of the configuration and the timing to load the configuration and linters are creating unmanageable situations.
|
||||
// There is no simple solution because it's spaghetti code.
|
||||
// I need to completely rewrite the command line system and the executor because it's extremely time consuming to debug,
|
||||
// so it's unmaintainable.
|
||||
e.cfg.Run.Go = commandLineCfg.Run.Go
|
||||
} else if e.cfg.Run.Go == "" {
|
||||
e.cfg.Run.Go = config.DetectGoVersion()
|
||||
}
|
||||
|
||||
// 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.goenv = goutil.NewEnv(e.log.Child(logutils.DebugKeyGoEnv))
|
||||
|
||||
e.fileCache = fsutils.NewFileCache()
|
||||
e.lineCache = fsutils.NewLineCache(e.fileCache)
|
||||
|
||||
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.contextLoader = lint.NewContextLoader(e.cfg, e.log.Child(logutils.DebugKeyLoader), e.goenv,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) Execute() error {
|
||||
return e.rootCmd.Execute()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return &cfg, 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
|
||||
}
|
||||
|
||||
s, err := fs.GetStringSlice(f.Name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
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 wh(text string) string {
|
||||
return color.GreenString(text)
|
||||
}
|
104
pkg/commands/flagsets.go
Normal file
104
pkg/commands/flagsets.go
Normal file
|
@ -0,0 +1,104 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/commands/internal"
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/exitcodes"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||
)
|
||||
|
||||
func setupLintersFlagSet(v *viper.Viper, fs *pflag.FlagSet) {
|
||||
fs.StringSliceP("disable", "D", nil, color.GreenString("Disable specific linter")) // Hack see Loader.applyStringSliceHack
|
||||
internal.AddFlagAndBind(v, fs, fs.Bool, "disable-all", "linters.disable-all", false, color.GreenString("Disable all linters"))
|
||||
|
||||
fs.StringSliceP("enable", "E", nil, color.GreenString("Enable specific linter")) // Hack see Loader.applyStringSliceHack
|
||||
internal.AddFlagAndBind(v, fs, fs.Bool, "enable-all", "linters.enable-all", false, color.GreenString("Enable all linters"))
|
||||
|
||||
internal.AddFlagAndBind(v, fs, fs.Bool, "fast", "linters.fast", false,
|
||||
color.GreenString("Enable only fast linters from enabled linters set (first run won't be fast)"))
|
||||
|
||||
// Hack see Loader.applyStringSliceHack
|
||||
fs.StringSliceP("presets", "p", nil,
|
||||
color.GreenString(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(), "|"))))
|
||||
}
|
||||
|
||||
func setupRunFlagSet(v *viper.Viper, fs *pflag.FlagSet) {
|
||||
internal.AddFlagAndBindP(v, fs, fs.IntP, "concurrency", "j", "run.concurrency", getDefaultConcurrency(),
|
||||
color.GreenString("Number of CPUs to use (Default: number of logical CPUs)"))
|
||||
|
||||
internal.AddFlagAndBind(v, fs, fs.String, "modules-download-mode", "run.modules-download-mode", "",
|
||||
color.GreenString("Modules download mode. If not empty, passed as -mod=<mode> to go tools"))
|
||||
internal.AddFlagAndBind(v, fs, fs.Int, "issues-exit-code", "run.issues-exit-code", exitcodes.IssuesFound,
|
||||
color.GreenString("Exit code when issues were found"))
|
||||
internal.AddFlagAndBind(v, fs, fs.String, "go", "run.go", "", color.GreenString("Targeted Go version"))
|
||||
fs.StringSlice("build-tags", nil, color.GreenString("Build tags")) // Hack see Loader.applyStringSliceHack
|
||||
|
||||
internal.AddFlagAndBind(v, fs, fs.Duration, "timeout", "run.timeout", defaultTimeout, color.GreenString("Timeout for total work"))
|
||||
|
||||
internal.AddFlagAndBind(v, fs, fs.Bool, "tests", "run.tests", true, color.GreenString("Analyze tests (*_test.go)"))
|
||||
fs.StringSlice("skip-dirs", nil, color.GreenString("Regexps of directories to skip")) // Hack see Loader.applyStringSliceHack
|
||||
internal.AddFlagAndBind(v, fs, fs.Bool, "skip-dirs-use-default", "run.skip-dirs-use-default", true, getDefaultDirectoryExcludeHelp())
|
||||
fs.StringSlice("skip-files", nil, color.GreenString("Regexps of files to skip")) // Hack see Loader.applyStringSliceHack
|
||||
|
||||
const allowParallelDesc = "Allow multiple parallel golangci-lint instances running. " +
|
||||
"If false (default) - golangci-lint acquires file lock on start."
|
||||
internal.AddFlagAndBind(v, fs, fs.Bool, "allow-parallel-runners", "run.allow-parallel-runners", false,
|
||||
color.GreenString(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."
|
||||
internal.AddFlagAndBind(v, fs, fs.Bool, "allow-serial-runners", "run.allow-serial-runners", false, color.GreenString(allowSerialDesc))
|
||||
internal.AddFlagAndBind(v, fs, fs.Bool, "show-stats", "run.show-stats", false, color.GreenString("Show statistics per linter"))
|
||||
}
|
||||
|
||||
func setupOutputFlagSet(v *viper.Viper, fs *pflag.FlagSet) {
|
||||
internal.AddFlagAndBind(v, fs, fs.String, "out-format", "output.format", config.OutFormatColoredLineNumber,
|
||||
color.GreenString(fmt.Sprintf("Format of output: %s", strings.Join(config.OutFormats, "|"))))
|
||||
internal.AddFlagAndBind(v, fs, fs.Bool, "print-issued-lines", "output.print-issued-lines", true,
|
||||
color.GreenString("Print lines of code with issue"))
|
||||
internal.AddFlagAndBind(v, fs, fs.Bool, "print-linter-name", "output.print-linter-name", true,
|
||||
color.GreenString("Print linter name in issue line"))
|
||||
internal.AddFlagAndBind(v, fs, fs.Bool, "uniq-by-line", "output.uniq-by-line", true,
|
||||
color.GreenString("Make issues output unique by line"))
|
||||
internal.AddFlagAndBind(v, fs, fs.Bool, "sort-results", "output.sort-results", false,
|
||||
color.GreenString("Sort linter results"))
|
||||
internal.AddFlagAndBind(v, fs, fs.String, "path-prefix", "output.path-prefix", "",
|
||||
color.GreenString("Path prefix to add to output"))
|
||||
}
|
||||
|
||||
//nolint:gomnd
|
||||
func setupIssuesFlagSet(v *viper.Viper, fs *pflag.FlagSet) {
|
||||
fs.StringSliceP("exclude", "e", nil, color.GreenString("Exclude issue by regexp")) // Hack see Loader.applyStringSliceHack
|
||||
internal.AddFlagAndBind(v, fs, fs.Bool, "exclude-use-default", "issues.exclude-use-default", true,
|
||||
getDefaultIssueExcludeHelp())
|
||||
internal.AddFlagAndBind(v, fs, fs.Bool, "exclude-case-sensitive", "issues.exclude-case-sensitive", false,
|
||||
color.GreenString("If set to true exclude and exclude rules regular expressions are case-sensitive"))
|
||||
|
||||
internal.AddFlagAndBind(v, fs, fs.Int, "max-issues-per-linter", "issues.max-issues-per-linter", 50,
|
||||
color.GreenString("Maximum issues count per one linter. Set to 0 to disable"))
|
||||
internal.AddFlagAndBind(v, fs, fs.Int, "max-same-issues", "issues.max-same-issues", 3,
|
||||
color.GreenString("Maximum count of issues with the same text. Set to 0 to disable"))
|
||||
|
||||
const newDesc = "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."
|
||||
internal.AddFlagAndBindP(v, fs, fs.BoolP, "new", "n", "issues.new", false, color.GreenString(newDesc))
|
||||
internal.AddFlagAndBind(v, fs, fs.String, "new-from-rev", "issues.new-from-rev", "",
|
||||
color.GreenString("Show only new issues created after git revision `REV`"))
|
||||
internal.AddFlagAndBind(v, fs, fs.String, "new-from-patch", "issues.new-from-patch", "",
|
||||
color.GreenString("Show only new issues created in git patch with file path `PATH`"))
|
||||
internal.AddFlagAndBind(v, fs, fs.Bool, "whole-files", "issues.whole-files", false,
|
||||
color.GreenString("Show issues in any part of update files (requires new-from-rev or new-from-patch)"))
|
||||
internal.AddFlagAndBind(v, fs, fs.Bool, "fix", "issues.fix", false,
|
||||
color.GreenString("Fix found issues (if it's supported by the linter)"))
|
||||
}
|
|
@ -8,12 +8,23 @@ import (
|
|||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||
)
|
||||
|
||||
func (e *Executor) initHelp() {
|
||||
type helpCommand struct {
|
||||
cmd *cobra.Command
|
||||
|
||||
dbManager *lintersdb.Manager
|
||||
|
||||
log logutils.Log
|
||||
}
|
||||
|
||||
func newHelpCommand(logger logutils.Log) *helpCommand {
|
||||
c := &helpCommand{log: logger}
|
||||
|
||||
helpCmd := &cobra.Command{
|
||||
Use: "help",
|
||||
Short: "Help",
|
||||
|
@ -23,20 +34,31 @@ func (e *Executor) initHelp() {
|
|||
},
|
||||
}
|
||||
|
||||
helpCmd.AddCommand(&cobra.Command{
|
||||
Use: "linters",
|
||||
Short: "Help about linters",
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
Run: e.executeHelp,
|
||||
})
|
||||
helpCmd.AddCommand(
|
||||
&cobra.Command{
|
||||
Use: "linters",
|
||||
Short: "Help about linters",
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
Run: c.execute,
|
||||
PreRun: c.preRun,
|
||||
},
|
||||
)
|
||||
|
||||
e.rootCmd.SetHelpCommand(helpCmd)
|
||||
c.cmd = helpCmd
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (e *Executor) executeHelp(_ *cobra.Command, _ []string) {
|
||||
func (c *helpCommand) preRun(_ *cobra.Command, _ []string) {
|
||||
// The command doesn't depend on the real configuration.
|
||||
// It just needs the list of all plugins and all presets.
|
||||
c.dbManager = lintersdb.NewManager(config.NewDefault(), c.log)
|
||||
}
|
||||
|
||||
func (c *helpCommand) execute(_ *cobra.Command, _ []string) {
|
||||
var enabledLCs, disabledLCs []*linter.Config
|
||||
for _, lc := range e.dbManager.GetAllSupportedLinterConfigs() {
|
||||
for _, lc := range c.dbManager.GetAllSupportedLinterConfigs() {
|
||||
if lc.Internal {
|
||||
continue
|
||||
}
|
||||
|
@ -49,13 +71,19 @@ func (e *Executor) executeHelp(_ *cobra.Command, _ []string) {
|
|||
}
|
||||
|
||||
color.Green("Enabled by default linters:\n")
|
||||
printLinterConfigs(enabledLCs)
|
||||
printLinters(enabledLCs)
|
||||
|
||||
color.Red("\nDisabled by default linters:\n")
|
||||
printLinterConfigs(disabledLCs)
|
||||
printLinters(disabledLCs)
|
||||
|
||||
color.Green("\nLinters presets:")
|
||||
c.printPresets()
|
||||
}
|
||||
|
||||
func (c *helpCommand) printPresets() {
|
||||
for _, p := range lintersdb.AllPresets() {
|
||||
linters := e.dbManager.GetAllLinterConfigsForPreset(p)
|
||||
linters := c.dbManager.GetAllLinterConfigsForPreset(p)
|
||||
|
||||
var linterNames []string
|
||||
for _, lc := range linters {
|
||||
if lc.Internal {
|
||||
|
@ -65,14 +93,16 @@ func (e *Executor) executeHelp(_ *cobra.Command, _ []string) {
|
|||
linterNames = append(linterNames, lc.Name())
|
||||
}
|
||||
sort.Strings(linterNames)
|
||||
fmt.Fprintf(logutils.StdOut, "%s: %s\n", color.YellowString(p), strings.Join(linterNames, ", "))
|
||||
|
||||
_, _ = fmt.Fprintf(logutils.StdOut, "%s: %s\n", color.YellowString(p), strings.Join(linterNames, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
func printLinterConfigs(lcs []*linter.Config) {
|
||||
func printLinters(lcs []*linter.Config) {
|
||||
sort.Slice(lcs, func(i, j int) bool {
|
||||
return lcs[i].Name() < lcs[j].Name()
|
||||
})
|
||||
|
||||
for _, lc := range lcs {
|
||||
altNamesStr := ""
|
||||
if len(lc.AlternativeNames) != 0 {
|
||||
|
@ -91,7 +121,7 @@ func printLinterConfigs(lcs []*linter.Config) {
|
|||
deprecatedMark = " [" + color.RedString("deprecated") + "]"
|
||||
}
|
||||
|
||||
fmt.Fprintf(logutils.StdOut, "%s%s%s: %s [fast: %t, auto-fix: %t]\n", color.YellowString(lc.Name()),
|
||||
altNamesStr, deprecatedMark, linterDescription, !lc.IsSlowLinter(), lc.CanAutoFix)
|
||||
_, _ = fmt.Fprintf(logutils.StdOut, "%s%s%s: %s [fast: %t, auto-fix: %t]\n",
|
||||
color.YellowString(lc.Name()), altNamesStr, deprecatedMark, linterDescription, !lc.IsSlowLinter(), lc.CanAutoFix)
|
||||
}
|
||||
}
|
||||
|
|
32
pkg/commands/internal/vibra.go
Normal file
32
pkg/commands/internal/vibra.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type FlagFunc[T any] func(name string, value T, usage string) *T
|
||||
|
||||
type FlagPFunc[T any] func(name, shorthand string, value T, usage string) *T
|
||||
|
||||
// AddFlagAndBind adds a Cobra/pflag flag and binds it with Viper.
|
||||
func AddFlagAndBind[T any](v *viper.Viper, fs *pflag.FlagSet, pfn FlagFunc[T], name, bind string, value T, usage string) {
|
||||
pfn(name, value, usage)
|
||||
|
||||
err := v.BindPFlag(bind, fs.Lookup(name))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to bind flag %s: %v", name, err))
|
||||
}
|
||||
}
|
||||
|
||||
// AddFlagAndBindP adds a Cobra/pflag flag and binds it with Viper.
|
||||
func AddFlagAndBindP[T any](v *viper.Viper, fs *pflag.FlagSet, pfn FlagPFunc[T], name, shorthand, bind string, value T, usage string) {
|
||||
pfn(name, shorthand, value, usage)
|
||||
|
||||
err := v.BindPFlag(bind, fs.Lookup(name))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to bind flag %s: %v", name, err))
|
||||
}
|
||||
}
|
|
@ -2,40 +2,78 @@ package commands
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||
)
|
||||
|
||||
func (e *Executor) initLinters() {
|
||||
type lintersOptions struct {
|
||||
config.LoaderOptions
|
||||
}
|
||||
|
||||
type lintersCommand struct {
|
||||
viper *viper.Viper
|
||||
cmd *cobra.Command
|
||||
|
||||
opts lintersOptions
|
||||
|
||||
cfg *config.Config
|
||||
|
||||
log logutils.Log
|
||||
|
||||
dbManager *lintersdb.Manager
|
||||
enabledLintersSet *lintersdb.EnabledSet
|
||||
}
|
||||
|
||||
func newLintersCommand(logger logutils.Log, cfg *config.Config) *lintersCommand {
|
||||
c := &lintersCommand{
|
||||
viper: viper.New(),
|
||||
cfg: cfg,
|
||||
log: logger,
|
||||
}
|
||||
|
||||
lintersCmd := &cobra.Command{
|
||||
Use: "linters",
|
||||
Short: "List current linters configuration",
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
RunE: e.executeLinters,
|
||||
RunE: c.execute,
|
||||
PreRunE: c.preRunE,
|
||||
}
|
||||
|
||||
fs := lintersCmd.Flags()
|
||||
fs.SortFlags = false // sort them as they are defined here
|
||||
|
||||
initConfigFileFlagSet(fs, &e.cfg.Run)
|
||||
initLintersFlagSet(fs, &e.cfg.Linters)
|
||||
setupConfigFileFlagSet(fs, &c.opts.LoaderOptions)
|
||||
setupLintersFlagSet(c.viper, fs)
|
||||
|
||||
e.rootCmd.AddCommand(lintersCmd)
|
||||
c.cmd = lintersCmd
|
||||
|
||||
e.lintersCmd = lintersCmd
|
||||
return c
|
||||
}
|
||||
|
||||
// executeLinters runs the 'linters' CLI command, which displays the supported linters.
|
||||
func (e *Executor) executeLinters(_ *cobra.Command, _ []string) error {
|
||||
enabledLintersMap, err := e.enabledLintersSet.GetEnabledLintersMap()
|
||||
func (c *lintersCommand) preRunE(cmd *cobra.Command, _ []string) error {
|
||||
loader := config.NewLoader(c.log.Child(logutils.DebugKeyConfigReader), c.viper, cmd.Flags(), c.opts.LoaderOptions, c.cfg)
|
||||
|
||||
if err := loader.Load(); err != nil {
|
||||
return fmt.Errorf("can't load config: %w", err)
|
||||
}
|
||||
|
||||
c.dbManager = lintersdb.NewManager(c.cfg, c.log)
|
||||
c.enabledLintersSet = lintersdb.NewEnabledSet(c.dbManager,
|
||||
lintersdb.NewValidator(c.dbManager), c.log.Child(logutils.DebugKeyLintersDB), c.cfg)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *lintersCommand) execute(_ *cobra.Command, _ []string) error {
|
||||
enabledLintersMap, err := c.enabledLintersSet.GetEnabledLintersMap()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get enabled linters: %w", err)
|
||||
}
|
||||
|
@ -43,7 +81,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 c.dbManager.GetAllSupportedLinterConfigs() {
|
||||
if lc.Internal {
|
||||
continue
|
||||
}
|
||||
|
@ -56,20 +94,9 @@ func (e *Executor) executeLinters(_ *cobra.Command, _ []string) error {
|
|||
}
|
||||
|
||||
color.Green("Enabled by your configuration linters:\n")
|
||||
printLinterConfigs(enabledLinters)
|
||||
printLinters(enabledLinters)
|
||||
color.Red("\nDisabled by your configuration linters:\n")
|
||||
printLinterConfigs(disabledLCs)
|
||||
printLinters(disabledLCs)
|
||||
|
||||
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(), "|"))))
|
||||
}
|
||||
|
|
|
@ -1,160 +1,168 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"runtime/trace"
|
||||
"strconv"
|
||||
"slices"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/exitcodes"
|
||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||
"github.com/golangci/golangci-lint/pkg/report"
|
||||
)
|
||||
|
||||
const (
|
||||
// envHelpRun value: "1".
|
||||
envHelpRun = "HELP_RUN"
|
||||
envMemProfileRate = "GL_MEM_PROFILE_RATE"
|
||||
)
|
||||
func Execute(info BuildInfo) error {
|
||||
return newRootCommand(info).Execute()
|
||||
}
|
||||
|
||||
type rootOptions struct {
|
||||
PrintVersion bool // Flag only.
|
||||
|
||||
Verbose bool // Flag only.
|
||||
Color string // Flag only.
|
||||
}
|
||||
|
||||
type rootCommand struct {
|
||||
cmd *cobra.Command
|
||||
opts rootOptions
|
||||
|
||||
log logutils.Log
|
||||
}
|
||||
|
||||
func newRootCommand(info BuildInfo) *rootCommand {
|
||||
c := &rootCommand{}
|
||||
|
||||
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 {
|
||||
if c.opts.PrintVersion {
|
||||
_ = printVersion(logutils.StdOut, info)
|
||||
return nil
|
||||
}
|
||||
|
||||
return cmd.Help()
|
||||
},
|
||||
PersistentPreRunE: e.persistentPreRun,
|
||||
PersistentPostRunE: e.persistentPostRun,
|
||||
}
|
||||
|
||||
initRootFlagSet(rootCmd.PersistentFlags(), e.cfg)
|
||||
fs := rootCmd.Flags()
|
||||
fs.BoolVar(&c.opts.PrintVersion, "version", false, color.GreenString("Print version"))
|
||||
|
||||
e.rootCmd = rootCmd
|
||||
setupRootPersistentFlags(rootCmd.PersistentFlags(), &c.opts)
|
||||
|
||||
reportData := &report.Data{}
|
||||
log := report.NewLogWrapper(logutils.NewStderrLog(logutils.DebugKeyEmpty), reportData)
|
||||
|
||||
// Dedicated configuration for each command to avoid side effects of bindings.
|
||||
rootCmd.AddCommand(
|
||||
newLintersCommand(log, config.NewDefault()).cmd,
|
||||
newRunCommand(log, config.NewDefault(), reportData, info).cmd,
|
||||
newCacheCommand().cmd,
|
||||
newConfigCommand(log).cmd,
|
||||
newVersionCommand(info).cmd,
|
||||
)
|
||||
|
||||
rootCmd.SetHelpCommand(newHelpCommand(log).cmd)
|
||||
|
||||
c.log = log
|
||||
c.cmd = rootCmd
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (e *Executor) persistentPreRun(_ *cobra.Command, _ []string) error {
|
||||
if e.cfg.Run.PrintVersion {
|
||||
_ = printVersion(logutils.StdOut, e.buildInfo)
|
||||
os.Exit(exitcodes.Success) // a return nil is not enough to stop the process because we are inside the `preRun`.
|
||||
func (c *rootCommand) Execute() error {
|
||||
err := setupLogger(c.log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
runtime.GOMAXPROCS(e.cfg.Run.Concurrency)
|
||||
return c.cmd.Execute()
|
||||
}
|
||||
|
||||
if e.cfg.Run.CPUProfilePath != "" {
|
||||
f, err := os.Create(e.cfg.Run.CPUProfilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create file %s: %w", e.cfg.Run.CPUProfilePath, err)
|
||||
}
|
||||
if err := pprof.StartCPUProfile(f); err != nil {
|
||||
return fmt.Errorf("can't start CPU profiling: %w", err)
|
||||
}
|
||||
func setupRootPersistentFlags(fs *pflag.FlagSet, opts *rootOptions) {
|
||||
fs.BoolVarP(&opts.Verbose, "verbose", "v", false, color.GreenString("Verbose output"))
|
||||
fs.StringVar(&opts.Color, "color", "auto", color.GreenString("Use color when printing; can be 'always', 'auto', or 'never'"))
|
||||
}
|
||||
|
||||
func setupLogger(logger logutils.Log) error {
|
||||
opts, err := forceRootParsePersistentFlags()
|
||||
if err != nil && !errors.Is(err, pflag.ErrHelp) {
|
||||
return err
|
||||
}
|
||||
|
||||
if e.cfg.Run.MemProfilePath != "" {
|
||||
if rate := os.Getenv(envMemProfileRate); rate != "" {
|
||||
runtime.MemProfileRate, _ = strconv.Atoi(rate)
|
||||
}
|
||||
if opts == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if e.cfg.Run.TracePath != "" {
|
||||
f, err := os.Create(e.cfg.Run.TracePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create file %s: %w", e.cfg.Run.TracePath, err)
|
||||
}
|
||||
if err = trace.Start(f); err != nil {
|
||||
return fmt.Errorf("can't start tracing: %w", err)
|
||||
}
|
||||
logutils.SetupVerboseLog(logger, opts.Verbose)
|
||||
|
||||
switch opts.Color {
|
||||
case "always":
|
||||
color.NoColor = false
|
||||
case "never":
|
||||
color.NoColor = true
|
||||
case "auto":
|
||||
// nothing
|
||||
default:
|
||||
logger.Fatalf("invalid value %q for --color; must be 'always', 'auto', or 'never'", opts.Color)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) persistentPostRun(_ *cobra.Command, _ []string) error {
|
||||
if e.cfg.Run.CPUProfilePath != "" {
|
||||
pprof.StopCPUProfile()
|
||||
}
|
||||
func forceRootParsePersistentFlags() (*rootOptions, 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)
|
||||
|
||||
if e.cfg.Run.MemProfilePath != "" {
|
||||
f, err := os.Create(e.cfg.Run.MemProfilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create file %s: %w", e.cfg.Run.MemProfilePath, err)
|
||||
// Ignore unknown flags because we will parse the command flags later.
|
||||
fs.ParseErrorsWhitelist = pflag.ParseErrorsWhitelist{UnknownFlags: true}
|
||||
|
||||
opts := &rootOptions{}
|
||||
|
||||
// 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,
|
||||
// to not affect main parsing by this parsing of only config option.
|
||||
setupRootPersistentFlags(fs, opts)
|
||||
|
||||
fs.Usage = func() {} // otherwise, help text will be printed twice
|
||||
|
||||
if err := fs.Parse(safeArgs(fs, os.Args)); err != nil {
|
||||
if errors.Is(err, pflag.ErrHelp) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ms runtime.MemStats
|
||||
runtime.ReadMemStats(&ms)
|
||||
printMemStats(&ms, e.log)
|
||||
return nil, fmt.Errorf("can't parse args: %w", err)
|
||||
}
|
||||
|
||||
if err := pprof.WriteHeapProfile(f); err != nil {
|
||||
return fmt.Errorf("can't write heap profile: %w", err)
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// Shorthands are a problem because pflag, with UnknownFlags, will try to parse all the letters as options.
|
||||
// A shorthand can aggregate several letters (ex `ps -aux`)
|
||||
// The function replaces non-supported shorthands by a dumb flag.
|
||||
func safeArgs(fs *pflag.FlagSet, args []string) []string {
|
||||
var shorthands []string
|
||||
fs.VisitAll(func(flag *pflag.Flag) {
|
||||
shorthands = append(shorthands, flag.Shorthand)
|
||||
})
|
||||
|
||||
var cleanArgs []string
|
||||
for _, arg := range args {
|
||||
if len(arg) > 1 && arg[0] == '-' && arg[1] != '-' && !slices.Contains(shorthands, string(arg[1])) {
|
||||
cleanArgs = append(cleanArgs, "--potato")
|
||||
continue
|
||||
}
|
||||
_ = f.Close()
|
||||
|
||||
cleanArgs = append(cleanArgs, arg)
|
||||
}
|
||||
|
||||
if e.cfg.Run.TracePath != "" {
|
||||
trace.Stop()
|
||||
}
|
||||
|
||||
os.Exit(e.exitCode)
|
||||
|
||||
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 "+
|
||||
"stack_in_use=%s stack_sys=%s "+
|
||||
"mspan_sys=%s mcache_sys=%s buck_hash_sys=%s gc_sys=%s other_sys=%s "+
|
||||
"mallocs_n=%d frees_n=%d heap_objects_n=%d gc_cpu_fraction=%.2f",
|
||||
formatMemory(ms.Alloc), formatMemory(ms.TotalAlloc), formatMemory(ms.Sys),
|
||||
formatMemory(ms.HeapAlloc), formatMemory(ms.HeapSys),
|
||||
formatMemory(ms.HeapIdle), formatMemory(ms.HeapReleased), formatMemory(ms.HeapInuse),
|
||||
formatMemory(ms.StackInuse), formatMemory(ms.StackSys),
|
||||
formatMemory(ms.MSpanSys), formatMemory(ms.MCacheSys), formatMemory(ms.BuckHashSys),
|
||||
formatMemory(ms.GCSys), formatMemory(ms.OtherSys),
|
||||
ms.Mallocs, ms.Frees, ms.HeapObjects, ms.GCCPUFraction)
|
||||
}
|
||||
|
||||
func formatMemory(memBytes uint64) string {
|
||||
const Kb = 1024
|
||||
const Mb = Kb * 1024
|
||||
|
||||
if memBytes < Kb {
|
||||
return fmt.Sprintf("%db", memBytes)
|
||||
}
|
||||
if memBytes < Mb {
|
||||
return fmt.Sprintf("%dkb", memBytes/Kb)
|
||||
}
|
||||
return fmt.Sprintf("%dmb", memBytes/Mb)
|
||||
}
|
||||
|
||||
func getDefaultConcurrency() int {
|
||||
if os.Getenv(envHelpRun) == "1" {
|
||||
// Make stable concurrency for generating help documentation.
|
||||
const prettyConcurrency = 8
|
||||
return prettyConcurrency
|
||||
}
|
||||
|
||||
return runtime.NumCPU()
|
||||
return cleanArgs
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -9,7 +11,10 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"runtime/trace"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -17,16 +22,25 @@ import (
|
|||
"github.com/gofrs/flock"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/exp/maps"
|
||||
"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/exitcodes"
|
||||
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis/load"
|
||||
"github.com/golangci/golangci-lint/pkg/goutil"
|
||||
"github.com/golangci/golangci-lint/pkg/lint"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||
"github.com/golangci/golangci-lint/pkg/packages"
|
||||
"github.com/golangci/golangci-lint/pkg/printers"
|
||||
"github.com/golangci/golangci-lint/pkg/report"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
"github.com/golangci/golangci-lint/pkg/timeutils"
|
||||
)
|
||||
|
||||
const defaultFileMode = 0644
|
||||
|
@ -40,20 +54,68 @@ const (
|
|||
envMemLogEvery = "GL_MEM_LOG_EVERY"
|
||||
)
|
||||
|
||||
func (e *Executor) initRun() {
|
||||
const (
|
||||
// envHelpRun value: "1".
|
||||
envHelpRun = "HELP_RUN"
|
||||
envMemProfileRate = "GL_MEM_PROFILE_RATE"
|
||||
)
|
||||
|
||||
type runOptions struct {
|
||||
config.LoaderOptions
|
||||
|
||||
CPUProfilePath string // Flag only.
|
||||
MemProfilePath string // Flag only.
|
||||
TracePath string // Flag only.
|
||||
|
||||
PrintResourcesUsage bool // Flag only.
|
||||
}
|
||||
|
||||
type runCommand struct {
|
||||
viper *viper.Viper
|
||||
cmd *cobra.Command
|
||||
|
||||
opts runOptions
|
||||
|
||||
cfg *config.Config
|
||||
|
||||
buildInfo BuildInfo
|
||||
|
||||
dbManager *lintersdb.Manager
|
||||
enabledLintersSet *lintersdb.EnabledSet
|
||||
|
||||
log logutils.Log
|
||||
debugf logutils.DebugFunc
|
||||
reportData *report.Data
|
||||
|
||||
contextLoader *lint.ContextLoader
|
||||
goenv *goutil.Env
|
||||
|
||||
fileCache *fsutils.FileCache
|
||||
lineCache *fsutils.LineCache
|
||||
|
||||
flock *flock.Flock
|
||||
|
||||
exitCode int
|
||||
}
|
||||
|
||||
func newRunCommand(logger logutils.Log, cfg *config.Config, reportData *report.Data, info BuildInfo) *runCommand {
|
||||
c := &runCommand{
|
||||
viper: viper.New(),
|
||||
log: logger,
|
||||
debugf: logutils.Debug(logutils.DebugKeyExec),
|
||||
cfg: cfg,
|
||||
reportData: reportData,
|
||||
buildInfo: info,
|
||||
}
|
||||
|
||||
runCmd := &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "Run the linters",
|
||||
Run: e.executeRun,
|
||||
PreRunE: func(_ *cobra.Command, _ []string) error {
|
||||
if ok := e.acquireFileLock(); !ok {
|
||||
return errors.New("parallel golangci-lint is running")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
PostRun: func(_ *cobra.Command, _ []string) {
|
||||
e.releaseFileLock()
|
||||
},
|
||||
Use: "run",
|
||||
Short: "Run the linters",
|
||||
Run: c.execute,
|
||||
PreRunE: c.preRunE,
|
||||
PostRun: c.postRun,
|
||||
PersistentPreRunE: c.persistentPreRunE,
|
||||
PersistentPostRunE: c.persistentPostRunE,
|
||||
}
|
||||
|
||||
runCmd.SetOut(logutils.StdOut) // use custom output to properly color it in Windows terminals
|
||||
|
@ -62,16 +124,90 @@ func (e *Executor) initRun() {
|
|||
fs := runCmd.Flags()
|
||||
fs.SortFlags = false // sort them as they are defined here
|
||||
|
||||
initRunFlagSet(fs, e.cfg)
|
||||
// Only for testing purpose.
|
||||
// Don't add other flags here.
|
||||
fs.BoolVar(&cfg.InternalCmdTest, "internal-cmd-test", false,
|
||||
color.GreenString("Option is used only for testing golangci-lint command, don't use it"))
|
||||
_ = fs.MarkHidden("internal-cmd-test")
|
||||
|
||||
e.rootCmd.AddCommand(runCmd)
|
||||
setupConfigFileFlagSet(fs, &c.opts.LoaderOptions)
|
||||
|
||||
e.runCmd = runCmd
|
||||
setupLintersFlagSet(c.viper, fs)
|
||||
setupRunFlagSet(c.viper, fs)
|
||||
setupOutputFlagSet(c.viper, fs)
|
||||
setupIssuesFlagSet(c.viper, fs)
|
||||
|
||||
setupRunPersistentFlags(runCmd.PersistentFlags(), &c.opts)
|
||||
|
||||
c.cmd = runCmd
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// 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
|
||||
func (c *runCommand) persistentPreRunE(cmd *cobra.Command, _ []string) error {
|
||||
if err := c.startTracing(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
loader := config.NewLoader(c.log.Child(logutils.DebugKeyConfigReader), c.viper, cmd.Flags(), c.opts.LoaderOptions, c.cfg)
|
||||
|
||||
if err := loader.Load(); err != nil {
|
||||
return fmt.Errorf("can't load config: %w", err)
|
||||
}
|
||||
|
||||
runtime.GOMAXPROCS(c.cfg.Run.Concurrency)
|
||||
|
||||
return c.startTracing()
|
||||
}
|
||||
|
||||
func (c *runCommand) persistentPostRunE(_ *cobra.Command, _ []string) error {
|
||||
if err := c.stopTracing(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
os.Exit(c.exitCode)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *runCommand) preRunE(_ *cobra.Command, _ []string) error {
|
||||
c.dbManager = lintersdb.NewManager(c.cfg, c.log)
|
||||
c.enabledLintersSet = lintersdb.NewEnabledSet(c.dbManager,
|
||||
lintersdb.NewValidator(c.dbManager), c.log.Child(logutils.DebugKeyLintersDB), c.cfg)
|
||||
|
||||
c.goenv = goutil.NewEnv(c.log.Child(logutils.DebugKeyGoEnv))
|
||||
|
||||
c.fileCache = fsutils.NewFileCache()
|
||||
c.lineCache = fsutils.NewLineCache(c.fileCache)
|
||||
|
||||
sw := timeutils.NewStopwatch("pkgcache", c.log.Child(logutils.DebugKeyStopwatch))
|
||||
|
||||
pkgCache, err := pkgcache.NewCache(sw, c.log.Child(logutils.DebugKeyPkgCache))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build packages cache: %w", err)
|
||||
}
|
||||
|
||||
c.contextLoader = lint.NewContextLoader(c.cfg, c.log.Child(logutils.DebugKeyLoader), c.goenv,
|
||||
c.lineCache, c.fileCache, pkgCache, load.NewGuard())
|
||||
|
||||
if err = initHashSalt(c.buildInfo.Version, c.cfg); err != nil {
|
||||
return fmt.Errorf("failed to init hash salt: %w", err)
|
||||
}
|
||||
|
||||
if ok := c.acquireFileLock(); !ok {
|
||||
return errors.New("parallel golangci-lint is running")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *runCommand) postRun(_ *cobra.Command, _ []string) {
|
||||
c.releaseFileLock()
|
||||
}
|
||||
|
||||
func (c *runCommand) execute(_ *cobra.Command, args []string) {
|
||||
needTrackResources := logutils.IsVerbose() || c.opts.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
|
||||
|
@ -79,96 +215,154 @@ func (e *Executor) executeRun(_ *cobra.Command, args []string) {
|
|||
}
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), e.cfg.Run.Timeout)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), c.cfg.Run.Timeout)
|
||||
defer cancel()
|
||||
|
||||
if needTrackResources {
|
||||
go watchResources(ctx, trackResourcesEndCh, e.log, e.debugf)
|
||||
go watchResources(ctx, trackResourcesEndCh, c.log, c.debugf)
|
||||
}
|
||||
|
||||
if err := e.runAndPrint(ctx, args); err != nil {
|
||||
e.log.Errorf("Running error: %s", err)
|
||||
if e.exitCode == exitcodes.Success {
|
||||
if err := c.runAndPrint(ctx, args); err != nil {
|
||||
c.log.Errorf("Running error: %s", err)
|
||||
if c.exitCode == exitcodes.Success {
|
||||
var exitErr *exitcodes.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
e.exitCode = exitErr.Code
|
||||
c.exitCode = exitErr.Code
|
||||
} else {
|
||||
e.exitCode = exitcodes.Failure
|
||||
c.exitCode = exitcodes.Failure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
e.setupExitCode(ctx)
|
||||
c.setupExitCode(ctx)
|
||||
}
|
||||
|
||||
func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
|
||||
if err := e.goenv.Discover(ctx); err != nil {
|
||||
e.log.Warnf("Failed to discover go env: %s", err)
|
||||
func (c *runCommand) startTracing() error {
|
||||
if c.opts.CPUProfilePath != "" {
|
||||
f, err := os.Create(c.opts.CPUProfilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create file %s: %w", c.opts.CPUProfilePath, err)
|
||||
}
|
||||
if err := pprof.StartCPUProfile(f); err != nil {
|
||||
return fmt.Errorf("can't start CPU profiling: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.opts.MemProfilePath != "" {
|
||||
if rate := os.Getenv(envMemProfileRate); rate != "" {
|
||||
runtime.MemProfileRate, _ = strconv.Atoi(rate)
|
||||
}
|
||||
}
|
||||
|
||||
if c.opts.TracePath != "" {
|
||||
f, err := os.Create(c.opts.TracePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create file %s: %w", c.opts.TracePath, err)
|
||||
}
|
||||
if err = trace.Start(f); err != nil {
|
||||
return fmt.Errorf("can't start tracing: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *runCommand) stopTracing() error {
|
||||
if c.opts.CPUProfilePath != "" {
|
||||
pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
if c.opts.MemProfilePath != "" {
|
||||
f, err := os.Create(c.opts.MemProfilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create file %s: %w", c.opts.MemProfilePath, err)
|
||||
}
|
||||
|
||||
var ms runtime.MemStats
|
||||
runtime.ReadMemStats(&ms)
|
||||
printMemStats(&ms, c.log)
|
||||
|
||||
if err := pprof.WriteHeapProfile(f); err != nil {
|
||||
return fmt.Errorf("can't write heap profile: %w", err)
|
||||
}
|
||||
_ = f.Close()
|
||||
}
|
||||
|
||||
if c.opts.TracePath != "" {
|
||||
trace.Stop()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *runCommand) runAndPrint(ctx context.Context, args []string) error {
|
||||
if err := c.goenv.Discover(ctx); err != nil {
|
||||
c.log.Warnf("Failed to discover go env: %s", err)
|
||||
}
|
||||
|
||||
if !logutils.HaveDebugTag(logutils.DebugKeyLintersOutput) {
|
||||
// Don't allow linters and loader to print anything
|
||||
log.SetOutput(io.Discard)
|
||||
savedStdout, savedStderr := e.setOutputToDevNull()
|
||||
savedStdout, savedStderr := c.setOutputToDevNull()
|
||||
defer func() {
|
||||
os.Stdout, os.Stderr = savedStdout, savedStderr
|
||||
}()
|
||||
}
|
||||
|
||||
issues, err := e.runAnalysis(ctx, args)
|
||||
issues, err := c.runAnalysis(ctx, args)
|
||||
if err != nil {
|
||||
return err // XXX: don't loose type
|
||||
}
|
||||
|
||||
formats := strings.Split(e.cfg.Output.Format, ",")
|
||||
formats := strings.Split(c.cfg.Output.Format, ",")
|
||||
for _, format := range formats {
|
||||
out := strings.SplitN(format, ":", 2)
|
||||
if len(out) < 2 {
|
||||
out = append(out, "")
|
||||
}
|
||||
|
||||
err := e.printReports(issues, out[1], out[0])
|
||||
err := c.printReports(issues, out[1], out[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
e.printStats(issues)
|
||||
c.printStats(issues)
|
||||
|
||||
e.setExitCodeIfIssuesFound(issues)
|
||||
c.setExitCodeIfIssuesFound(issues)
|
||||
|
||||
e.fileCache.PrintStats(e.log)
|
||||
c.fileCache.PrintStats(c.log)
|
||||
|
||||
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
|
||||
func (c *runCommand) runAnalysis(ctx context.Context, args []string) ([]result.Issue, error) {
|
||||
c.cfg.Run.Args = args
|
||||
|
||||
lintersToRun, err := e.enabledLintersSet.GetOptimizedLinters()
|
||||
lintersToRun, err := c.enabledLintersSet.GetOptimizedLinters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enabledLintersMap, err := e.enabledLintersSet.GetEnabledLintersMap()
|
||||
enabledLintersMap, err := c.enabledLintersSet.GetEnabledLintersMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, lc := range e.dbManager.GetAllSupportedLinterConfigs() {
|
||||
for _, lc := range c.dbManager.GetAllSupportedLinterConfigs() {
|
||||
isEnabled := enabledLintersMap[lc.Name()] != nil
|
||||
e.reportData.AddLinter(lc.Name(), isEnabled, lc.EnabledByDefault)
|
||||
c.reportData.AddLinter(lc.Name(), isEnabled, lc.EnabledByDefault)
|
||||
}
|
||||
|
||||
lintCtx, err := e.contextLoader.Load(ctx, lintersToRun)
|
||||
lintCtx, err := c.contextLoader.Load(ctx, lintersToRun)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("context loading failed: %w", err)
|
||||
}
|
||||
lintCtx.Log = e.log.Child(logutils.DebugKeyLintersContext)
|
||||
lintCtx.Log = c.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)
|
||||
runner, err := lint.NewRunner(c.cfg, c.log.Child(logutils.DebugKeyRunner),
|
||||
c.goenv, c.enabledLintersSet, c.lineCache, c.fileCache, c.dbManager, lintCtx.Packages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -176,11 +370,11 @@ func (e *Executor) runAnalysis(ctx context.Context, args []string) ([]result.Iss
|
|||
return runner.Run(ctx, lintersToRun, lintCtx)
|
||||
}
|
||||
|
||||
func (e *Executor) setOutputToDevNull() (savedStdout, savedStderr *os.File) {
|
||||
func (c *runCommand) 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)
|
||||
c.log.Warnf("Can't open null device %q: %s", os.DevNull, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -188,19 +382,19 @@ func (e *Executor) setOutputToDevNull() (savedStdout, savedStderr *os.File) {
|
|||
return
|
||||
}
|
||||
|
||||
func (e *Executor) setExitCodeIfIssuesFound(issues []result.Issue) {
|
||||
func (c *runCommand) setExitCodeIfIssuesFound(issues []result.Issue) {
|
||||
if len(issues) != 0 {
|
||||
e.exitCode = e.cfg.Run.ExitCodeIfIssuesFound
|
||||
c.exitCode = c.cfg.Run.ExitCodeIfIssuesFound
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) printReports(issues []result.Issue, path, format string) error {
|
||||
w, shouldClose, err := e.createWriter(path)
|
||||
func (c *runCommand) printReports(issues []result.Issue, path, format string) error {
|
||||
w, shouldClose, err := c.createWriter(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create output for %s: %w", path, err)
|
||||
}
|
||||
|
||||
p, err := e.createPrinter(format, w)
|
||||
p, err := c.createPrinter(format, w)
|
||||
if err != nil {
|
||||
if file, ok := w.(io.Closer); shouldClose && ok {
|
||||
_ = file.Close()
|
||||
|
@ -222,7 +416,7 @@ func (e *Executor) printReports(issues []result.Issue, path, format string) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) createWriter(path string) (io.Writer, bool, error) {
|
||||
func (c *runCommand) createWriter(path string) (io.Writer, bool, error) {
|
||||
if path == "" || path == "stdout" {
|
||||
return logutils.StdOut, false, nil
|
||||
}
|
||||
|
@ -236,19 +430,19 @@ func (e *Executor) createWriter(path string) (io.Writer, bool, error) {
|
|||
return f, true, nil
|
||||
}
|
||||
|
||||
func (e *Executor) createPrinter(format string, w io.Writer) (printers.Printer, error) {
|
||||
func (c *runCommand) createPrinter(format string, w io.Writer) (printers.Printer, error) {
|
||||
var p printers.Printer
|
||||
switch format {
|
||||
case config.OutFormatJSON:
|
||||
p = printers.NewJSON(&e.reportData, w)
|
||||
p = printers.NewJSON(c.reportData, w)
|
||||
case config.OutFormatColoredLineNumber, config.OutFormatLineNumber:
|
||||
p = printers.NewText(e.cfg.Output.PrintIssuedLine,
|
||||
format == config.OutFormatColoredLineNumber, e.cfg.Output.PrintLinterName,
|
||||
e.log.Child(logutils.DebugKeyTextPrinter), w)
|
||||
p = printers.NewText(c.cfg.Output.PrintIssuedLine,
|
||||
format == config.OutFormatColoredLineNumber, c.cfg.Output.PrintLinterName,
|
||||
c.log.Child(logutils.DebugKeyTextPrinter), w)
|
||||
case config.OutFormatTab, config.OutFormatColoredTab:
|
||||
p = printers.NewTab(e.cfg.Output.PrintLinterName,
|
||||
p = printers.NewTab(c.cfg.Output.PrintLinterName,
|
||||
format == config.OutFormatColoredTab,
|
||||
e.log.Child(logutils.DebugKeyTabPrinter), w)
|
||||
c.log.Child(logutils.DebugKeyTabPrinter), w)
|
||||
case config.OutFormatCheckstyle:
|
||||
p = printers.NewCheckstyle(w)
|
||||
case config.OutFormatCodeClimate:
|
||||
|
@ -268,13 +462,13 @@ func (e *Executor) createPrinter(format string, w io.Writer) (printers.Printer,
|
|||
return p, nil
|
||||
}
|
||||
|
||||
func (e *Executor) printStats(issues []result.Issue) {
|
||||
if !e.cfg.Run.ShowStats {
|
||||
func (c *runCommand) printStats(issues []result.Issue) {
|
||||
if !c.cfg.Run.ShowStats {
|
||||
return
|
||||
}
|
||||
|
||||
if len(issues) == 0 {
|
||||
e.runCmd.Println("0 issues.")
|
||||
c.cmd.Println("0 issues.")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -283,53 +477,53 @@ func (e *Executor) printStats(issues []result.Issue) {
|
|||
stats[issues[idx].FromLinter]++
|
||||
}
|
||||
|
||||
e.runCmd.Printf("%d issues:\n", len(issues))
|
||||
c.cmd.Printf("%d issues:\n", len(issues))
|
||||
|
||||
keys := maps.Keys(stats)
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
e.runCmd.Printf("* %s: %d\n", key, stats[key])
|
||||
c.cmd.Printf("* %s: %d\n", key, stats[key])
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) setupExitCode(ctx context.Context) {
|
||||
func (c *runCommand) setupExitCode(ctx context.Context) {
|
||||
if ctx.Err() != nil {
|
||||
e.exitCode = exitcodes.Timeout
|
||||
e.log.Errorf("Timeout exceeded: try increasing it by passing --timeout option")
|
||||
c.exitCode = exitcodes.Timeout
|
||||
c.log.Errorf("Timeout exceeded: try increasing it by passing --timeout option")
|
||||
return
|
||||
}
|
||||
|
||||
if e.exitCode != exitcodes.Success {
|
||||
if c.exitCode != exitcodes.Success {
|
||||
return
|
||||
}
|
||||
|
||||
needFailOnWarnings := os.Getenv(lintersdb.EnvTestRun) == "1" || os.Getenv(envFailOnWarnings) == "1"
|
||||
if needFailOnWarnings && len(e.reportData.Warnings) != 0 {
|
||||
e.exitCode = exitcodes.WarningInTest
|
||||
if needFailOnWarnings && len(c.reportData.Warnings) != 0 {
|
||||
c.exitCode = exitcodes.WarningInTest
|
||||
return
|
||||
}
|
||||
|
||||
if e.reportData.Error != "" {
|
||||
if c.reportData.Error != "" {
|
||||
// it's a case e.g. when typecheck linter couldn't parse and error and just logged it
|
||||
e.exitCode = exitcodes.ErrorWasLogged
|
||||
c.exitCode = exitcodes.ErrorWasLogged
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) acquireFileLock() bool {
|
||||
if e.cfg.Run.AllowParallelRunners {
|
||||
e.debugf("Parallel runners are allowed, no locking")
|
||||
func (c *runCommand) acquireFileLock() bool {
|
||||
if c.cfg.Run.AllowParallelRunners {
|
||||
c.debugf("Parallel runners are allowed, no locking")
|
||||
return true
|
||||
}
|
||||
|
||||
lockFile := filepath.Join(os.TempDir(), "golangci-lint.lock")
|
||||
e.debugf("Locking on file %s...", lockFile)
|
||||
c.debugf("Locking on file %s...", lockFile)
|
||||
f := flock.New(lockFile)
|
||||
const retryDelay = time.Second
|
||||
|
||||
ctx := context.Background()
|
||||
if !e.cfg.Run.AllowSerialRunners {
|
||||
if !c.cfg.Run.AllowSerialRunners {
|
||||
const totalTimeout = 5 * time.Second
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, totalTimeout)
|
||||
|
@ -339,108 +533,23 @@ func (e *Executor) acquireFileLock() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
e.flock = f
|
||||
c.flock = f
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *Executor) releaseFileLock() {
|
||||
if e.cfg.Run.AllowParallelRunners {
|
||||
func (c *runCommand) releaseFileLock() {
|
||||
if c.cfg.Run.AllowParallelRunners {
|
||||
return
|
||||
}
|
||||
|
||||
if err := e.flock.Unlock(); err != nil {
|
||||
e.debugf("Failed to unlock on file: %s", err)
|
||||
if err := c.flock.Unlock(); err != nil {
|
||||
c.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)
|
||||
if err := os.Remove(c.flock.Path()); err != nil {
|
||||
c.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")
|
||||
|
@ -497,6 +606,11 @@ func watchResources(ctx context.Context, done chan struct{}, logger logutils.Log
|
|||
close(done)
|
||||
}
|
||||
|
||||
func setupConfigFileFlagSet(fs *pflag.FlagSet, cfg *config.LoaderOptions) {
|
||||
fs.StringVarP(&cfg.Config, "config", "c", "", color.GreenString("Read config from file path `PATH`"))
|
||||
fs.BoolVar(&cfg.NoConfig, "no-config", false, color.GreenString("Don't read config file"))
|
||||
}
|
||||
|
||||
func getDefaultIssueExcludeHelp() string {
|
||||
parts := []string{color.GreenString("Use or not use default excludes:")}
|
||||
for _, ep := range config.DefaultExcludePatterns {
|
||||
|
@ -517,3 +631,115 @@ func getDefaultDirectoryExcludeHelp() string {
|
|||
parts = append(parts, "")
|
||||
return strings.Join(parts, "\n")
|
||||
}
|
||||
|
||||
func setupRunPersistentFlags(fs *pflag.FlagSet, opts *runOptions) {
|
||||
fs.BoolVar(&opts.PrintResourcesUsage, "print-resources-usage", false,
|
||||
color.GreenString("Print avg and max memory usage of golangci-lint and total time"))
|
||||
|
||||
fs.StringVar(&opts.CPUProfilePath, "cpu-profile-path", "", color.GreenString("Path to CPU profile output file"))
|
||||
fs.StringVar(&opts.MemProfilePath, "mem-profile-path", "", color.GreenString("Path to memory profile output file"))
|
||||
fs.StringVar(&opts.TracePath, "trace-path", "", color.GreenString("Path to trace output file"))
|
||||
}
|
||||
|
||||
func getDefaultConcurrency() int {
|
||||
if os.Getenv(envHelpRun) == "1" {
|
||||
// Make stable concurrency for generating help documentation.
|
||||
const prettyConcurrency = 8
|
||||
return prettyConcurrency
|
||||
}
|
||||
|
||||
return runtime.NumCPU()
|
||||
}
|
||||
|
||||
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 "+
|
||||
"stack_in_use=%s stack_sys=%s "+
|
||||
"mspan_sys=%s mcache_sys=%s buck_hash_sys=%s gc_sys=%s other_sys=%s "+
|
||||
"mallocs_n=%d frees_n=%d heap_objects_n=%d gc_cpu_fraction=%.2f",
|
||||
formatMemory(ms.Alloc), formatMemory(ms.TotalAlloc), formatMemory(ms.Sys),
|
||||
formatMemory(ms.HeapAlloc), formatMemory(ms.HeapSys),
|
||||
formatMemory(ms.HeapIdle), formatMemory(ms.HeapReleased), formatMemory(ms.HeapInuse),
|
||||
formatMemory(ms.StackInuse), formatMemory(ms.StackSys),
|
||||
formatMemory(ms.MSpanSys), formatMemory(ms.MCacheSys), formatMemory(ms.BuckHashSys),
|
||||
formatMemory(ms.GCSys), formatMemory(ms.OtherSys),
|
||||
ms.Mallocs, ms.Frees, ms.HeapObjects, ms.GCCPUFraction)
|
||||
}
|
||||
|
||||
func formatMemory(memBytes uint64) string {
|
||||
const Kb = 1024
|
||||
const Mb = Kb * 1024
|
||||
|
||||
if memBytes < Kb {
|
||||
return fmt.Sprintf("%db", memBytes)
|
||||
}
|
||||
if memBytes < Mb {
|
||||
return fmt.Sprintf("%dkb", memBytes/Kb)
|
||||
}
|
||||
return fmt.Sprintf("%dmb", memBytes/Mb)
|
||||
}
|
||||
|
||||
// --- Related to cache.
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -8,10 +8,8 @@ import (
|
|||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
)
|
||||
|
||||
type BuildInfo struct {
|
||||
|
@ -26,65 +24,75 @@ type versionInfo struct {
|
|||
BuildInfo *debug.BuildInfo
|
||||
}
|
||||
|
||||
func (e *Executor) initVersion() {
|
||||
type versionOptions struct {
|
||||
Format string `mapstructure:"format"`
|
||||
Debug bool `mapstructure:"debug"`
|
||||
}
|
||||
|
||||
type versionCommand struct {
|
||||
cmd *cobra.Command
|
||||
opts versionOptions
|
||||
|
||||
info BuildInfo
|
||||
}
|
||||
|
||||
func newVersionCommand(info BuildInfo) *versionCommand {
|
||||
c := &versionCommand{info: info}
|
||||
|
||||
versionCmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Version",
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: cobra.NoFileCompletions,
|
||||
RunE: e.executeVersion,
|
||||
RunE: c.execute,
|
||||
}
|
||||
|
||||
fs := versionCmd.Flags()
|
||||
fs.SortFlags = false // sort them as they are defined here
|
||||
|
||||
initVersionFlagSet(fs, e.cfg)
|
||||
fs.StringVar(&c.opts.Format, "format", "", color.GreenString("The version's format can be: 'short', 'json'"))
|
||||
fs.BoolVar(&c.opts.Debug, "debug", false, color.GreenString("Add build information"))
|
||||
|
||||
e.rootCmd.AddCommand(versionCmd)
|
||||
c.cmd = versionCmd
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (e *Executor) executeVersion(_ *cobra.Command, _ []string) error {
|
||||
if e.cfg.Version.Debug {
|
||||
func (c *versionCommand) execute(_ *cobra.Command, _ []string) error {
|
||||
if c.opts.Debug {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch strings.ToLower(e.cfg.Version.Format) {
|
||||
switch strings.ToLower(c.opts.Format) {
|
||||
case "json":
|
||||
return json.NewEncoder(os.Stdout).Encode(versionInfo{
|
||||
Info: e.buildInfo,
|
||||
Info: c.info,
|
||||
BuildInfo: info,
|
||||
})
|
||||
|
||||
default:
|
||||
fmt.Println(info.String())
|
||||
return printVersion(os.Stdout, e.buildInfo)
|
||||
return printVersion(os.Stdout, c.info)
|
||||
}
|
||||
}
|
||||
|
||||
switch strings.ToLower(e.cfg.Version.Format) {
|
||||
switch strings.ToLower(c.opts.Format) {
|
||||
case "short":
|
||||
fmt.Println(e.buildInfo.Version)
|
||||
fmt.Println(c.info.Version)
|
||||
return nil
|
||||
|
||||
case "json":
|
||||
return json.NewEncoder(os.Stdout).Encode(e.buildInfo)
|
||||
return json.NewEncoder(os.Stdout).Encode(c.info)
|
||||
|
||||
default:
|
||||
return printVersion(os.Stdout, e.buildInfo)
|
||||
return printVersion(os.Stdout, c.info)
|
||||
}
|
||||
}
|
||||
|
||||
func initVersionFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
|
||||
// Version config
|
||||
vc := &cfg.Version
|
||||
fs.StringVar(&vc.Format, "format", "", wh("The version's format can be: 'short', 'json'"))
|
||||
fs.BoolVar(&vc.Debug, "debug", false, wh("Add build information"))
|
||||
}
|
||||
|
||||
func printVersion(w io.Writer, buildInfo BuildInfo) error {
|
||||
func printVersion(w io.Writer, info 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)
|
||||
info.Version, info.GoVersion, info.Commit, info.Date)
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue