@@ -2,20 +2,17 @@ package commands
22
33import (
44 "context"
5- "errors"
65 "fmt"
76 "go/token"
87 "io/ioutil"
98 "log"
109 "os"
11- "path/filepath"
1210 "runtime"
1311 "strings"
1412 "time"
1513
1614 "github.com/fatih/color"
1715 "github.com/golangci/golangci-lint/pkg/config"
18- "github.com/golangci/golangci-lint/pkg/fsutils"
1916 "github.com/golangci/golangci-lint/pkg/lint"
2017 "github.com/golangci/golangci-lint/pkg/lint/lintersdb"
2118 "github.com/golangci/golangci-lint/pkg/printers"
@@ -24,7 +21,6 @@ import (
2421 "github.com/sirupsen/logrus"
2522 "github.com/spf13/cobra"
2623 "github.com/spf13/pflag"
27- "github.com/spf13/viper"
2824)
2925
3026const (
@@ -48,15 +44,15 @@ func wh(text string) string {
4844 return color .GreenString (text )
4945}
5046
51- func ( e * Executor ) initFlagSet (fs * pflag.FlagSet ) {
47+ func initFlagSet (fs * pflag.FlagSet , cfg * config. Config ) {
5248 hideFlag := func (name string ) {
5349 if err := fs .MarkHidden (name ); err != nil {
5450 panic (err )
5551 }
5652 }
5753
5854 // Output config
59- oc := & e . cfg .Output
55+ oc := & cfg .Output
6056 fs .StringVar (& oc .Format , "out-format" ,
6157 config .OutFormatColoredLineNumber ,
6258 wh (fmt .Sprintf ("Format of output: %s" , strings .Join (config .OutFormats , "|" ))))
@@ -66,18 +62,18 @@ func (e *Executor) initFlagSet(fs *pflag.FlagSet) {
6662 hideFlag ("print-welcome" ) // no longer used
6763
6864 // Run config
69- rc := & e . cfg .Run
65+ rc := & cfg .Run
7066 fs .IntVar (& rc .ExitCodeIfIssuesFound , "issues-exit-code" ,
7167 1 , wh ("Exit code when issues were found" ))
72- fs .StringSliceVar (& rc .BuildTags , "build-tags" , [] string {} , wh ("Build tags (not all linters support them)" ))
68+ fs .StringSliceVar (& rc .BuildTags , "build-tags" , nil , wh ("Build tags (not all linters support them)" ))
7369 fs .DurationVar (& rc .Deadline , "deadline" , time .Minute , wh ("Deadline for total work" ))
7470 fs .BoolVar (& rc .AnalyzeTests , "tests" , true , wh ("Analyze tests (*_test.go)" ))
7571 fs .BoolVar (& rc .PrintResourcesUsage , "print-resources-usage" , false , wh ("Print avg and max memory usage of golangci-lint and total time" ))
7672 fs .StringVarP (& rc .Config , "config" , "c" , "" , wh ("Read config from file path `PATH`" ))
7773 fs .BoolVar (& rc .NoConfig , "no-config" , false , wh ("Don't read config" ))
7874
7975 // Linters settings config
80- lsc := & e . cfg .LintersSettings
76+ lsc := & cfg .LintersSettings
8177
8278 // Hide all linters settings flags: they were initially visible,
8379 // but when number of linters started to grow it became ovious that
@@ -126,18 +122,18 @@ func (e *Executor) initFlagSet(fs *pflag.FlagSet) {
126122 hideFlag ("depguard.include-go-root" )
127123
128124 // Linters config
129- lc := & e . cfg .Linters
130- fs .StringSliceVarP (& lc .Enable , "enable" , "E" , [] string {} , wh ("Enable specific linter" ))
131- fs .StringSliceVarP (& lc .Disable , "disable" , "D" , [] string {} , wh ("Disable specific linter" ))
125+ lc := & cfg .Linters
126+ fs .StringSliceVarP (& lc .Enable , "enable" , "E" , nil , wh ("Enable specific linter" ))
127+ fs .StringSliceVarP (& lc .Disable , "disable" , "D" , nil , wh ("Disable specific linter" ))
132128 fs .BoolVar (& lc .EnableAll , "enable-all" , false , wh ("Enable all linters" ))
133129 fs .BoolVar (& lc .DisableAll , "disable-all" , false , wh ("Disable all linters" ))
134- fs .StringSliceVarP (& lc .Presets , "presets" , "p" , [] string {} ,
130+ fs .StringSliceVarP (& lc .Presets , "presets" , "p" , nil ,
135131 wh (fmt .Sprintf ("Enable presets (%s) of linters. Run 'golangci-lint linters' to see them. This option implies option --disable-all" , strings .Join (lintersdb .AllPresets (), "|" ))))
136132 fs .BoolVar (& lc .Fast , "fast" , false , wh ("Run only fast linters from enabled linters set" ))
137133
138134 // Issues config
139- ic := & e . cfg .Issues
140- fs .StringSliceVarP (& ic .ExcludePatterns , "exclude" , "e" , [] string {} , wh ("Exclude issue by regexp" ))
135+ ic := & cfg .Issues
136+ fs .StringSliceVarP (& ic .ExcludePatterns , "exclude" , "e" , nil , wh ("Exclude issue by regexp" ))
141137 fs .BoolVar (& ic .UseDefaultExcludes , "exclude-use-default" , true , getDefaultExcludeHelp ())
142138
143139 fs .IntVar (& ic .MaxIssuesPerLinter , "max-issues-per-linter" , 50 , wh ("Maximum issues count per one linter. Set to 0 to disable" ))
@@ -162,9 +158,15 @@ func (e *Executor) initRun() {
162158
163159 fs := runCmd .Flags ()
164160 fs .SortFlags = false // sort them as they are defined here
165- e . initFlagSet (fs )
161+ initFlagSet (fs , e . cfg )
166162
163+ // init e.cfg by values from config: flags parse will see these values
164+ // like the default ones. It will overwrite them only if the same option
165+ // is found in command-line: it's ok, command-line has higher priority.
167166 e .parseConfig ()
167+
168+ // Slice options must be explicitly set for properly merging.
169+ fixSlicesFlags (fs )
168170}
169171
170172func (e * Executor ) runAnalysis (ctx context.Context , args []string ) (<- chan result.Issue , error ) {
@@ -289,172 +291,6 @@ func (e *Executor) executeRun(cmd *cobra.Command, args []string) {
289291 }
290292}
291293
292- func (e * Executor ) parseConfig () {
293- // XXX: hack with double parsing for 2 purposes:
294- // 1. to access "config" option here.
295- // 2. to give config less priority than command line.
296-
297- // We use another pflag.FlagSet here to not set `changed` flag
298- // on cmd.Flags() options. Otherwise string slice options will be duplicated.
299- fs := pflag .NewFlagSet ("config flag set" , pflag .ContinueOnError )
300-
301- // Don't do `fs.AddFlagSet(cmd.Flags())` because it shared flags representations:
302- // `changed` variable inside string slice vars will be shared.
303- e .initFlagSet (fs )
304- e .initRootFlagSet (fs )
305-
306- fs .Usage = func () {} // otherwise help text will be printed twice
307- if err := fs .Parse (os .Args ); err != nil {
308- if err == pflag .ErrHelp {
309- return
310- }
311- logrus .Fatalf ("Can't parse args: %s" , err )
312- }
313-
314- e .setupLog () // for `-v` to work until running of preRun function
315-
316- if err := viper .BindPFlags (fs ); err != nil {
317- logrus .Fatalf ("Can't bind cobra's flags to viper: %s" , err )
318- }
319-
320- viper .SetEnvPrefix ("GOLANGCI" )
321- viper .SetEnvKeyReplacer (strings .NewReplacer ("." , "_" ))
322- viper .AutomaticEnv ()
323-
324- configFile := e .cfg .Run .Config
325- if e .cfg .Run .NoConfig && configFile != "" {
326- logrus .Fatal ("can't combine option --config and --no-config" )
327- }
328-
329- if e .cfg .Run .NoConfig {
330- return
331- }
332-
333- if configFile != "" {
334- viper .SetConfigFile (configFile )
335- } else {
336- setupConfigFileSearch (fs .Args ())
337- }
338-
339- e .parseConfigImpl ()
340- }
341-
342- func setupConfigFileSearch (args []string ) {
343- // skip all args ([golangci-lint, run/linters]) before files/dirs list
344- for len (args ) != 0 {
345- if args [0 ] == "run" {
346- args = args [1 :]
347- break
348- }
349-
350- args = args [1 :]
351- }
352-
353- // find first file/dir arg
354- firstArg := "./..."
355- if len (args ) != 0 {
356- firstArg = args [0 ]
357- }
358-
359- absStartPath , err := filepath .Abs (firstArg )
360- if err != nil {
361- logrus .Infof ("Can't make abs path for %q: %s" , firstArg , err )
362- absStartPath = filepath .Clean (firstArg )
363- }
364-
365- // start from it
366- var curDir string
367- if fsutils .IsDir (absStartPath ) {
368- curDir = absStartPath
369- } else {
370- curDir = filepath .Dir (absStartPath )
371- }
372-
373- // find all dirs from it up to the root
374- configSearchPaths := []string {"./" }
375- for {
376- configSearchPaths = append (configSearchPaths , curDir )
377- newCurDir := filepath .Dir (curDir )
378- if curDir == newCurDir || newCurDir == "" {
379- break
380- }
381- curDir = newCurDir
382- }
383-
384- logrus .Infof ("Config search paths: %s" , configSearchPaths )
385- viper .SetConfigName (".golangci" )
386- for _ , p := range configSearchPaths {
387- viper .AddConfigPath (p )
388- }
389- }
390-
391- func getRelPath (p string ) string {
392- wd , err := os .Getwd ()
393- if err != nil {
394- logrus .Infof ("Can't get wd: %s" , err )
395- return p
396- }
397-
398- r , err := filepath .Rel (wd , p )
399- if err != nil {
400- logrus .Infof ("Can't make path %s relative to %s: %s" , p , wd , err )
401- return p
402- }
403-
404- return r
405- }
406-
407- func (e * Executor ) parseConfigImpl () {
408- commandLineConfig := * e .cfg // make copy
409-
410- if err := viper .ReadInConfig (); err != nil {
411- if _ , ok := err .(viper.ConfigFileNotFoundError ); ok {
412- return
413- }
414- logrus .Fatalf ("Can't read viper config: %s" , err )
415- }
416-
417- usedConfigFile := viper .ConfigFileUsed ()
418- if usedConfigFile == "" {
419- return
420- }
421- logrus .Infof ("Used config file %s" , getRelPath (usedConfigFile ))
422-
423- if err := viper .Unmarshal (& e .cfg ); err != nil {
424- logrus .Fatalf ("Can't unmarshal config by viper: %s" , err )
425- }
426-
427- if err := e .validateConfig (& commandLineConfig ); err != nil {
428- logrus .Fatal (err )
429- }
430-
431- if e .cfg .InternalTest { // just for testing purposes: to detect config file usage
432- fmt .Fprintln (printers .StdOut , "test" )
433- os .Exit (0 )
434- }
435- }
436-
437- func (e * Executor ) validateConfig (commandLineConfig * config.Config ) error {
438- c := e .cfg
439- if len (c .Run .Args ) != 0 {
440- return errors .New ("option run.args in config isn't supported now" )
441- }
442-
443- if commandLineConfig .Run .CPUProfilePath == "" && c .Run .CPUProfilePath != "" {
444- return errors .New ("option run.cpuprofilepath in config isn't allowed" )
445- }
446-
447- if commandLineConfig .Run .MemProfilePath == "" && c .Run .MemProfilePath != "" {
448- return errors .New ("option run.memprofilepath in config isn't allowed" )
449- }
450-
451- if ! commandLineConfig .Run .IsVerbose && c .Run .IsVerbose {
452- return errors .New ("can't set run.verbose option with config: only on command-line" )
453- }
454-
455- return nil
456- }
457-
458294func watchResources (ctx context.Context , done chan struct {}) {
459295 startedAt := time .Now ()
460296
0 commit comments