217 lines
5.1 KiB
Go
217 lines
5.1 KiB
Go
package commands
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/golangci/golangci-lint/pkg/config"
|
|
"github.com/golangci/golangci-lint/pkg/fsutils"
|
|
"github.com/golangci/golangci-lint/pkg/printers"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/spf13/pflag"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
func (e *Executor) parseConfigImpl() {
|
|
if err := viper.ReadInConfig(); err != nil {
|
|
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
|
return
|
|
}
|
|
logrus.Fatalf("Can't read viper config: %s", err)
|
|
}
|
|
|
|
usedConfigFile := viper.ConfigFileUsed()
|
|
if usedConfigFile == "" {
|
|
return
|
|
}
|
|
logrus.Infof("Used config file %s", getRelPath(usedConfigFile))
|
|
|
|
if err := viper.Unmarshal(&e.cfg); err != nil {
|
|
logrus.Fatalf("Can't unmarshal config by viper: %s", err)
|
|
}
|
|
|
|
if err := e.validateConfig(); err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
if e.cfg.InternalTest { // just for testing purposes: to detect config file usage
|
|
fmt.Fprintln(printers.StdOut, "test")
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
func (e *Executor) validateConfig() error {
|
|
c := e.cfg
|
|
if len(c.Run.Args) != 0 {
|
|
return errors.New("option run.args in config isn't supported now")
|
|
}
|
|
|
|
if c.Run.CPUProfilePath != "" {
|
|
return errors.New("option run.cpuprofilepath in config isn't allowed")
|
|
}
|
|
|
|
if c.Run.MemProfilePath != "" {
|
|
return errors.New("option run.memprofilepath in config isn't allowed")
|
|
}
|
|
|
|
if c.Run.IsVerbose {
|
|
return errors.New("can't set run.verbose option with config: only on command-line")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func setupConfigFileSearch(args []string) {
|
|
// skip all args ([golangci-lint, run/linters]) before files/dirs list
|
|
for len(args) != 0 {
|
|
if args[0] == "run" {
|
|
args = args[1:]
|
|
break
|
|
}
|
|
|
|
args = args[1:]
|
|
}
|
|
|
|
// find first file/dir arg
|
|
firstArg := "./..."
|
|
if len(args) != 0 {
|
|
firstArg = args[0]
|
|
}
|
|
|
|
absStartPath, err := filepath.Abs(firstArg)
|
|
if err != nil {
|
|
logrus.Infof("Can't make abs path for %q: %s", firstArg, err)
|
|
absStartPath = filepath.Clean(firstArg)
|
|
}
|
|
|
|
// start from it
|
|
var curDir string
|
|
if fsutils.IsDir(absStartPath) {
|
|
curDir = absStartPath
|
|
} else {
|
|
curDir = filepath.Dir(absStartPath)
|
|
}
|
|
|
|
// find all dirs from it up to the root
|
|
configSearchPaths := []string{"./"}
|
|
for {
|
|
configSearchPaths = append(configSearchPaths, curDir)
|
|
newCurDir := filepath.Dir(curDir)
|
|
if curDir == newCurDir || newCurDir == "" {
|
|
break
|
|
}
|
|
curDir = newCurDir
|
|
}
|
|
|
|
logrus.Infof("Config search paths: %s", configSearchPaths)
|
|
viper.SetConfigName(".golangci")
|
|
for _, p := range configSearchPaths {
|
|
viper.AddConfigPath(p)
|
|
}
|
|
}
|
|
|
|
func getRelPath(p string) string {
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
logrus.Infof("Can't get wd: %s", err)
|
|
return p
|
|
}
|
|
|
|
r, err := filepath.Rel(wd, p)
|
|
if err != nil {
|
|
logrus.Infof("Can't make path %s relative to %s: %s", p, wd, err)
|
|
return p
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
func (e *Executor) needVersionOption() bool {
|
|
return e.date != ""
|
|
}
|
|
|
|
func parseConfigOption() (string, []string, 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)
|
|
|
|
// 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.
|
|
var cfg config.Config
|
|
initFlagSet(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 err == pflag.ErrHelp {
|
|
return "", nil, err
|
|
}
|
|
|
|
logrus.Fatalf("Can't parse args: %s", err)
|
|
}
|
|
|
|
setupLog(cfg.Run.IsVerbose) // for `-v` to work until running of preRun function
|
|
|
|
configFile := cfg.Run.Config
|
|
if cfg.Run.NoConfig && configFile != "" {
|
|
logrus.Fatal("can't combine option --config and --no-config")
|
|
}
|
|
|
|
if cfg.Run.NoConfig {
|
|
return "", nil, fmt.Errorf("no need to use config")
|
|
}
|
|
|
|
return configFile, fs.Args(), nil
|
|
}
|
|
|
|
func (e *Executor) parseConfig() {
|
|
// XXX: hack with double parsing for 2 purposes:
|
|
// 1. to access "config" option here.
|
|
// 2. to give config less priority than command line.
|
|
|
|
configFile, restArgs, err := parseConfigOption()
|
|
if err != nil {
|
|
return // skippable error, e.g. --no-config
|
|
}
|
|
|
|
if configFile != "" {
|
|
viper.SetConfigFile(configFile)
|
|
} else {
|
|
setupConfigFileSearch(restArgs)
|
|
}
|
|
|
|
e.parseConfigImpl()
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// calling Set sets Changed to true: next Set calls will append, not overwrite
|
|
_ = f.Value.Set(strings.Join(s, ","))
|
|
})
|
|
}
|