github.com/nozzle/golangci-lint@v1.49.0-nz3/pkg/commands/run.go (about)

     1  package commands
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"log"
     8  	"os"
     9  	"runtime"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/fatih/color"
    14  	"github.com/pkg/errors"
    15  	"github.com/spf13/cobra"
    16  	"github.com/spf13/pflag"
    17  
    18  	"github.com/golangci/golangci-lint/pkg/config"
    19  	"github.com/golangci/golangci-lint/pkg/exitcodes"
    20  	"github.com/golangci/golangci-lint/pkg/lint"
    21  	"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
    22  	"github.com/golangci/golangci-lint/pkg/logutils"
    23  	"github.com/golangci/golangci-lint/pkg/packages"
    24  	"github.com/golangci/golangci-lint/pkg/printers"
    25  	"github.com/golangci/golangci-lint/pkg/result"
    26  	"github.com/golangci/golangci-lint/pkg/result/processors"
    27  )
    28  
    29  const defaultFileMode = 0644
    30  
    31  const (
    32  	// envFailOnWarnings value: "1"
    33  	envFailOnWarnings = "FAIL_ON_WARNINGS"
    34  	// envMemLogEvery value: "1"
    35  	envMemLogEvery = "GL_MEM_LOG_EVERY"
    36  )
    37  
    38  func getDefaultIssueExcludeHelp() string {
    39  	parts := []string{"Use or not use default excludes:"}
    40  	for _, ep := range config.DefaultExcludePatterns {
    41  		parts = append(parts,
    42  			fmt.Sprintf("  # %s %s: %s", ep.ID, ep.Linter, ep.Why),
    43  			fmt.Sprintf("  - %s", color.YellowString(ep.Pattern)),
    44  			"",
    45  		)
    46  	}
    47  	return strings.Join(parts, "\n")
    48  }
    49  
    50  func getDefaultDirectoryExcludeHelp() string {
    51  	parts := []string{"Use or not use default excluded directories:"}
    52  	for _, dir := range packages.StdExcludeDirRegexps {
    53  		parts = append(parts, fmt.Sprintf("  - %s", color.YellowString(dir)))
    54  	}
    55  	parts = append(parts, "")
    56  	return strings.Join(parts, "\n")
    57  }
    58  
    59  func wh(text string) string {
    60  	return color.GreenString(text)
    61  }
    62  
    63  const defaultTimeout = time.Minute
    64  
    65  //nolint:funlen,gomnd
    66  func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, isFinalInit bool) {
    67  	hideFlag := func(name string) {
    68  		if err := fs.MarkHidden(name); err != nil {
    69  			panic(err)
    70  		}
    71  
    72  		// we run initFlagSet multiple times, but we wouldn't like to see deprecation message multiple times
    73  		if isFinalInit {
    74  			const deprecateMessage = "flag will be removed soon, please, use .golangci.yml config"
    75  			if err := fs.MarkDeprecated(name, deprecateMessage); err != nil {
    76  				panic(err)
    77  			}
    78  		}
    79  	}
    80  
    81  	// Output config
    82  	oc := &cfg.Output
    83  	fs.StringVar(&oc.Format, "out-format",
    84  		config.OutFormatColoredLineNumber,
    85  		wh(fmt.Sprintf("Format of output: %s", strings.Join(config.OutFormats, "|"))))
    86  	fs.BoolVar(&oc.PrintIssuedLine, "print-issued-lines", true, wh("Print lines of code with issue"))
    87  	fs.BoolVar(&oc.PrintLinterName, "print-linter-name", true, wh("Print linter name in issue line"))
    88  	fs.BoolVar(&oc.UniqByLine, "uniq-by-line", true, wh("Make issues output unique by line"))
    89  	fs.BoolVar(&oc.SortResults, "sort-results", false, wh("Sort linter results"))
    90  	fs.BoolVar(&oc.PrintWelcomeMessage, "print-welcome", false, wh("Print welcome message"))
    91  	fs.StringVar(&oc.PathPrefix, "path-prefix", "", wh("Path prefix to add to output"))
    92  	hideFlag("print-welcome") // no longer used
    93  
    94  	fs.BoolVar(&cfg.InternalCmdTest, "internal-cmd-test", false, wh("Option is used only for testing golangci-lint command, don't use it"))
    95  	if err := fs.MarkHidden("internal-cmd-test"); err != nil {
    96  		panic(err)
    97  	}
    98  
    99  	// Run config
   100  	rc := &cfg.Run
   101  	fs.StringVar(&rc.ModulesDownloadMode, "modules-download-mode", "",
   102  		"Modules download mode. If not empty, passed as -mod=<mode> to go tools")
   103  	fs.IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code",
   104  		exitcodes.IssuesFound, wh("Exit code when issues were found"))
   105  	fs.StringVar(&rc.Go, "go", "", wh("Targeted Go version"))
   106  	fs.StringSliceVar(&rc.BuildTags, "build-tags", nil, wh("Build tags"))
   107  
   108  	fs.DurationVar(&rc.Timeout, "deadline", defaultTimeout, wh("Deadline for total work"))
   109  	if err := fs.MarkHidden("deadline"); err != nil {
   110  		panic(err)
   111  	}
   112  	fs.DurationVar(&rc.Timeout, "timeout", defaultTimeout, wh("Timeout for total work"))
   113  
   114  	fs.BoolVar(&rc.AnalyzeTests, "tests", true, wh("Analyze tests (*_test.go)"))
   115  	fs.BoolVar(&rc.PrintResourcesUsage, "print-resources-usage", false,
   116  		wh("Print avg and max memory usage of golangci-lint and total time"))
   117  	fs.StringVarP(&rc.Config, "config", "c", "", wh("Read config from file path `PATH`"))
   118  	fs.BoolVar(&rc.NoConfig, "no-config", false, wh("Don't read config"))
   119  	fs.StringSliceVar(&rc.SkipDirs, "skip-dirs", nil, wh("Regexps of directories to skip"))
   120  	fs.BoolVar(&rc.UseDefaultSkipDirs, "skip-dirs-use-default", true, getDefaultDirectoryExcludeHelp())
   121  	fs.StringSliceVar(&rc.SkipFiles, "skip-files", nil, wh("Regexps of files to skip"))
   122  
   123  	const allowParallelDesc = "Allow multiple parallel golangci-lint instances running. " +
   124  		"If false (default) - golangci-lint acquires file lock on start."
   125  	fs.BoolVar(&rc.AllowParallelRunners, "allow-parallel-runners", false, wh(allowParallelDesc))
   126  	const allowSerialDesc = "Allow multiple golangci-lint instances running, but serialize them	around a lock. " +
   127  		"If false (default) - golangci-lint exits with an error if it fails to acquire file lock on start."
   128  	fs.BoolVar(&rc.AllowSerialRunners, "allow-serial-runners", false, wh(allowSerialDesc))
   129  
   130  	// Linters settings config
   131  	lsc := &cfg.LintersSettings
   132  
   133  	// Hide all linters settings flags: they were initially visible,
   134  	// but when number of linters started to grow it became obvious that
   135  	// we can't fill 90% of flags by linters settings: common flags became hard to find.
   136  	// New linters settings should be done only through config file.
   137  	fs.BoolVar(&lsc.Errcheck.CheckTypeAssertions, "errcheck.check-type-assertions",
   138  		false, "Errcheck: check for ignored type assertion results")
   139  	hideFlag("errcheck.check-type-assertions")
   140  	fs.BoolVar(&lsc.Errcheck.CheckAssignToBlank, "errcheck.check-blank", false,
   141  		"Errcheck: check for errors assigned to blank identifier: _ = errFunc()")
   142  	hideFlag("errcheck.check-blank")
   143  	fs.StringVar(&lsc.Errcheck.Exclude, "errcheck.exclude", "",
   144  		"Path to a file containing a list of functions to exclude from checking")
   145  	hideFlag("errcheck.exclude")
   146  	fs.StringVar(&lsc.Errcheck.Ignore, "errcheck.ignore", "fmt:.*",
   147  		`Comma-separated list of pairs of the form pkg:regex. The regex is used to ignore names within pkg`)
   148  	hideFlag("errcheck.ignore")
   149  
   150  	fs.BoolVar(&lsc.Govet.CheckShadowing, "govet.check-shadowing", false,
   151  		"Govet: check for shadowed variables")
   152  	hideFlag("govet.check-shadowing")
   153  
   154  	fs.Float64Var(&lsc.Golint.MinConfidence, "golint.min-confidence", 0.8,
   155  		"Golint: minimum confidence of a problem to print it")
   156  	hideFlag("golint.min-confidence")
   157  
   158  	fs.BoolVar(&lsc.Gofmt.Simplify, "gofmt.simplify", true, "Gofmt: simplify code")
   159  	hideFlag("gofmt.simplify")
   160  
   161  	fs.IntVar(&lsc.Gocyclo.MinComplexity, "gocyclo.min-complexity",
   162  		30, "Minimal complexity of function to report it")
   163  	hideFlag("gocyclo.min-complexity")
   164  
   165  	fs.BoolVar(&lsc.Maligned.SuggestNewOrder, "maligned.suggest-new", false,
   166  		"Maligned: print suggested more optimal struct fields ordering")
   167  	hideFlag("maligned.suggest-new")
   168  
   169  	fs.IntVar(&lsc.Dupl.Threshold, "dupl.threshold",
   170  		150, "Dupl: Minimal threshold to detect copy-paste")
   171  	hideFlag("dupl.threshold")
   172  
   173  	fs.BoolVar(&lsc.Goconst.MatchWithConstants, "goconst.match-constant",
   174  		true, "Goconst: look for existing constants matching the values")
   175  	hideFlag("goconst.match-constant")
   176  	fs.IntVar(&lsc.Goconst.MinStringLen, "goconst.min-len",
   177  		3, "Goconst: minimum constant string length")
   178  	hideFlag("goconst.min-len")
   179  	fs.IntVar(&lsc.Goconst.MinOccurrencesCount, "goconst.min-occurrences",
   180  		3, "Goconst: minimum occurrences of constant string count to trigger issue")
   181  	hideFlag("goconst.min-occurrences")
   182  	fs.BoolVar(&lsc.Goconst.ParseNumbers, "goconst.numbers",
   183  		false, "Goconst: search also for duplicated numbers")
   184  	hideFlag("goconst.numbers")
   185  	fs.IntVar(&lsc.Goconst.NumberMin, "goconst.min",
   186  		3, "minimum value, only works with goconst.numbers")
   187  	hideFlag("goconst.min")
   188  	fs.IntVar(&lsc.Goconst.NumberMax, "goconst.max",
   189  		3, "maximum value, only works with goconst.numbers")
   190  	hideFlag("goconst.max")
   191  	fs.BoolVar(&lsc.Goconst.IgnoreCalls, "goconst.ignore-calls",
   192  		true, "Goconst: ignore when constant is not used as function argument")
   193  	hideFlag("goconst.ignore-calls")
   194  
   195  	// (@dixonwille) These flag is only used for testing purposes.
   196  	fs.StringSliceVar(&lsc.Depguard.Packages, "depguard.packages", nil,
   197  		"Depguard: packages to add to the list")
   198  	hideFlag("depguard.packages")
   199  
   200  	fs.BoolVar(&lsc.Depguard.IncludeGoRoot, "depguard.include-go-root", false,
   201  		"Depguard: check list against standard lib")
   202  	hideFlag("depguard.include-go-root")
   203  
   204  	fs.IntVar(&lsc.Lll.TabWidth, "lll.tab-width", 1,
   205  		"Lll: tab width in spaces")
   206  	hideFlag("lll.tab-width")
   207  
   208  	// Linters config
   209  	lc := &cfg.Linters
   210  	fs.StringSliceVarP(&lc.Enable, "enable", "E", nil, wh("Enable specific linter"))
   211  	fs.StringSliceVarP(&lc.Disable, "disable", "D", nil, wh("Disable specific linter"))
   212  	fs.BoolVar(&lc.EnableAll, "enable-all", false, wh("Enable all linters"))
   213  
   214  	fs.BoolVar(&lc.DisableAll, "disable-all", false, wh("Disable all linters"))
   215  	fs.StringSliceVarP(&lc.Presets, "presets", "p", nil,
   216  		wh(fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint linters' to see "+
   217  			"them. This option implies option --disable-all", strings.Join(m.AllPresets(), "|"))))
   218  	fs.BoolVar(&lc.Fast, "fast", false, wh("Run only fast linters from enabled linters set (first run won't be fast)"))
   219  
   220  	// Issues config
   221  	ic := &cfg.Issues
   222  	fs.StringSliceVarP(&ic.ExcludePatterns, "exclude", "e", nil, wh("Exclude issue by regexp"))
   223  	fs.BoolVar(&ic.UseDefaultExcludes, "exclude-use-default", true, getDefaultIssueExcludeHelp())
   224  	fs.BoolVar(&ic.ExcludeCaseSensitive, "exclude-case-sensitive", false, wh("If set to true exclude "+
   225  		"and exclude rules regular expressions are case sensitive"))
   226  
   227  	fs.IntVar(&ic.MaxIssuesPerLinter, "max-issues-per-linter", 50,
   228  		wh("Maximum issues count per one linter. Set to 0 to disable"))
   229  	fs.IntVar(&ic.MaxSameIssues, "max-same-issues", 3,
   230  		wh("Maximum count of issues with the same text. Set to 0 to disable"))
   231  
   232  	fs.BoolVarP(&ic.Diff, "new", "n", false,
   233  		wh("Show only new issues: if there are unstaged changes or untracked files, only those changes "+
   234  			"are analyzed, else only changes in HEAD~ are analyzed.\nIt's a super-useful option for integration "+
   235  			"of golangci-lint into existing large codebase.\nIt's not practical to fix all existing issues at "+
   236  			"the moment of integration: much better to not allow issues in new code.\nFor CI setups, prefer "+
   237  			"--new-from-rev=HEAD~, as --new can skip linting the current patch if any scripts generate "+
   238  			"unstaged files before golangci-lint runs."))
   239  	fs.StringVar(&ic.DiffFromRevision, "new-from-rev", "",
   240  		wh("Show only new issues created after git revision `REV`"))
   241  	fs.StringVar(&ic.DiffPatchFilePath, "new-from-patch", "",
   242  		wh("Show only new issues created in git patch with file path `PATH`"))
   243  	fs.BoolVar(&ic.WholeFiles, "whole-files", false,
   244  		wh("Show issues in any part of update files (requires new-from-rev or new-from-patch)"))
   245  	fs.BoolVar(&ic.NeedFix, "fix", false, "Fix found issues (if it's supported by the linter)")
   246  }
   247  
   248  func (e *Executor) initRunConfiguration(cmd *cobra.Command) {
   249  	fs := cmd.Flags()
   250  	fs.SortFlags = false // sort them as they are defined here
   251  	initFlagSet(fs, e.cfg, e.DBManager, true)
   252  }
   253  
   254  func (e *Executor) getConfigForCommandLine() (*config.Config, error) {
   255  	// We use another pflag.FlagSet here to not set `changed` flag
   256  	// on cmd.Flags() options. Otherwise, string slice options will be duplicated.
   257  	fs := pflag.NewFlagSet("config flag set", pflag.ContinueOnError)
   258  
   259  	var cfg config.Config
   260  	// Don't do `fs.AddFlagSet(cmd.Flags())` because it shares flags representations:
   261  	// `changed` variable inside string slice vars will be shared.
   262  	// Use another config variable here, not e.cfg, to not
   263  	// affect main parsing by this parsing of only config option.
   264  	initFlagSet(fs, &cfg, e.DBManager, false)
   265  	initVersionFlagSet(fs, &cfg)
   266  
   267  	// Parse max options, even force version option: don't want
   268  	// to get access to Executor here: it's error-prone to use
   269  	// cfg vs e.cfg.
   270  	initRootFlagSet(fs, &cfg, true)
   271  
   272  	fs.Usage = func() {} // otherwise, help text will be printed twice
   273  	if err := fs.Parse(os.Args); err != nil {
   274  		if err == pflag.ErrHelp {
   275  			return nil, err
   276  		}
   277  
   278  		return nil, fmt.Errorf("can't parse args: %s", err)
   279  	}
   280  
   281  	return &cfg, nil
   282  }
   283  
   284  func (e *Executor) initRun() {
   285  	e.runCmd = &cobra.Command{
   286  		Use:   "run",
   287  		Short: "Run the linters",
   288  		Run:   e.executeRun,
   289  		PreRunE: func(_ *cobra.Command, _ []string) error {
   290  			if ok := e.acquireFileLock(); !ok {
   291  				return errors.New("parallel golangci-lint is running")
   292  			}
   293  			return nil
   294  		},
   295  		PostRun: func(_ *cobra.Command, _ []string) {
   296  			e.releaseFileLock()
   297  		},
   298  	}
   299  	e.rootCmd.AddCommand(e.runCmd)
   300  
   301  	e.runCmd.SetOut(logutils.StdOut) // use custom output to properly color it in Windows terminals
   302  	e.runCmd.SetErr(logutils.StdErr)
   303  
   304  	e.initRunConfiguration(e.runCmd)
   305  }
   306  
   307  func fixSlicesFlags(fs *pflag.FlagSet) {
   308  	// It's a dirty hack to set flag.Changed to true for every string slice flag.
   309  	// It's necessary to merge config and command-line slices: otherwise command-line
   310  	// flags will always overwrite ones from the config.
   311  	fs.VisitAll(func(f *pflag.Flag) {
   312  		if f.Value.Type() != "stringSlice" {
   313  			return
   314  		}
   315  
   316  		s, err := fs.GetStringSlice(f.Name)
   317  		if err != nil {
   318  			return
   319  		}
   320  
   321  		if s == nil { // assume that every string slice flag has nil as the default
   322  			return
   323  		}
   324  
   325  		var safe []string
   326  		for _, v := range s {
   327  			// add quotes to escape comma because spf13/pflag use a CSV parser:
   328  			// https://github.com/spf13/pflag/blob/85dd5c8bc61cfa382fecd072378089d4e856579d/string_slice.go#L43
   329  			safe = append(safe, `"`+v+`"`)
   330  		}
   331  
   332  		// calling Set sets Changed to true: next Set calls will append, not overwrite
   333  		_ = f.Value.Set(strings.Join(safe, ","))
   334  	})
   335  }
   336  
   337  // runAnalysis executes the linters that have been enabled in the configuration.
   338  func (e *Executor) runAnalysis(ctx context.Context, args []string) ([]result.Issue, error) {
   339  	e.cfg.Run.Args = args
   340  
   341  	lintersToRun, err := e.EnabledLintersSet.GetOptimizedLinters()
   342  	if err != nil {
   343  		return nil, err
   344  	}
   345  
   346  	enabledLintersMap, err := e.EnabledLintersSet.GetEnabledLintersMap()
   347  	if err != nil {
   348  		return nil, err
   349  	}
   350  
   351  	for _, lc := range e.DBManager.GetAllSupportedLinterConfigs() {
   352  		isEnabled := enabledLintersMap[lc.Name()] != nil
   353  		e.reportData.AddLinter(lc.Name(), isEnabled, lc.EnabledByDefault)
   354  	}
   355  
   356  	lintCtx, err := e.contextLoader.Load(ctx, lintersToRun)
   357  	if err != nil {
   358  		return nil, errors.Wrap(err, "context loading failed")
   359  	}
   360  	lintCtx.Log = e.log.Child(logutils.DebugKeyLintersContext)
   361  
   362  	runner, err := lint.NewRunner(e.cfg, e.log.Child(logutils.DebugKeyRunner),
   363  		e.goenv, e.EnabledLintersSet, e.lineCache, e.DBManager, lintCtx.Packages)
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  
   368  	issues, err := runner.Run(ctx, lintersToRun, lintCtx)
   369  	if err != nil {
   370  		return nil, err
   371  	}
   372  
   373  	fixer := processors.NewFixer(e.cfg, e.log, e.fileCache)
   374  	return fixer.Process(issues), nil
   375  }
   376  
   377  func (e *Executor) setOutputToDevNull() (savedStdout, savedStderr *os.File) {
   378  	savedStdout, savedStderr = os.Stdout, os.Stderr
   379  	devNull, err := os.Open(os.DevNull)
   380  	if err != nil {
   381  		e.log.Warnf("Can't open null device %q: %s", os.DevNull, err)
   382  		return
   383  	}
   384  
   385  	os.Stdout, os.Stderr = devNull, devNull
   386  	return
   387  }
   388  
   389  func (e *Executor) setExitCodeIfIssuesFound(issues []result.Issue) {
   390  	if len(issues) != 0 {
   391  		e.exitCode = e.cfg.Run.ExitCodeIfIssuesFound
   392  	}
   393  }
   394  
   395  func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
   396  	if err := e.goenv.Discover(ctx); err != nil {
   397  		e.log.Warnf("Failed to discover go env: %s", err)
   398  	}
   399  
   400  	if !logutils.HaveDebugTag(logutils.DebugKeyLintersOutput) {
   401  		// Don't allow linters and loader to print anything
   402  		log.SetOutput(io.Discard)
   403  		savedStdout, savedStderr := e.setOutputToDevNull()
   404  		defer func() {
   405  			os.Stdout, os.Stderr = savedStdout, savedStderr
   406  		}()
   407  	}
   408  
   409  	issues, err := e.runAnalysis(ctx, args)
   410  	if err != nil {
   411  		return err // XXX: don't loose type
   412  	}
   413  
   414  	formats := strings.Split(e.cfg.Output.Format, ",")
   415  	for _, format := range formats {
   416  		out := strings.SplitN(format, ":", 2)
   417  		if len(out) < 2 {
   418  			out = append(out, "")
   419  		}
   420  
   421  		err := e.printReports(ctx, issues, out[1], out[0])
   422  		if err != nil {
   423  			return err
   424  		}
   425  	}
   426  
   427  	e.setExitCodeIfIssuesFound(issues)
   428  
   429  	e.fileCache.PrintStats(e.log)
   430  
   431  	return nil
   432  }
   433  
   434  func (e *Executor) printReports(ctx context.Context, issues []result.Issue, path, format string) error {
   435  	w, shouldClose, err := e.createWriter(path)
   436  	if err != nil {
   437  		return fmt.Errorf("can't create output for %s: %w", path, err)
   438  	}
   439  
   440  	p, err := e.createPrinter(format, w)
   441  	if err != nil {
   442  		if file, ok := w.(io.Closer); shouldClose && ok {
   443  			_ = file.Close()
   444  		}
   445  		return err
   446  	}
   447  
   448  	if err = p.Print(ctx, issues); err != nil {
   449  		if file, ok := w.(io.Closer); shouldClose && ok {
   450  			_ = file.Close()
   451  		}
   452  		return fmt.Errorf("can't print %d issues: %s", len(issues), err)
   453  	}
   454  
   455  	if file, ok := w.(io.Closer); shouldClose && ok {
   456  		_ = file.Close()
   457  	}
   458  
   459  	return nil
   460  }
   461  
   462  func (e *Executor) createWriter(path string) (io.Writer, bool, error) {
   463  	if path == "" || path == "stdout" {
   464  		return logutils.StdOut, false, nil
   465  	}
   466  	if path == "stderr" {
   467  		return logutils.StdErr, false, nil
   468  	}
   469  	f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, defaultFileMode)
   470  	if err != nil {
   471  		return nil, false, err
   472  	}
   473  	return f, true, nil
   474  }
   475  
   476  func (e *Executor) createPrinter(format string, w io.Writer) (printers.Printer, error) {
   477  	var p printers.Printer
   478  	switch format {
   479  	case config.OutFormatJSON:
   480  		p = printers.NewJSON(&e.reportData, w)
   481  	case config.OutFormatColoredLineNumber, config.OutFormatLineNumber:
   482  		p = printers.NewText(e.cfg.Output.PrintIssuedLine,
   483  			format == config.OutFormatColoredLineNumber, e.cfg.Output.PrintLinterName,
   484  			e.log.Child(logutils.DebugKeyTextPrinter), w)
   485  	case config.OutFormatTab:
   486  		p = printers.NewTab(e.cfg.Output.PrintLinterName, e.log.Child(logutils.DebugKeyTabPrinter), w)
   487  	case config.OutFormatCheckstyle:
   488  		p = printers.NewCheckstyle(w)
   489  	case config.OutFormatCodeClimate:
   490  		p = printers.NewCodeClimate(w)
   491  	case config.OutFormatHTML:
   492  		p = printers.NewHTML(w)
   493  	case config.OutFormatJunitXML:
   494  		p = printers.NewJunitXML(w)
   495  	case config.OutFormatGithubActions:
   496  		p = printers.NewGithub(w)
   497  	default:
   498  		return nil, fmt.Errorf("unknown output format %s", format)
   499  	}
   500  
   501  	return p, nil
   502  }
   503  
   504  // executeRun executes the 'run' CLI command, which runs the linters.
   505  func (e *Executor) executeRun(_ *cobra.Command, args []string) {
   506  	needTrackResources := e.cfg.Run.IsVerbose || e.cfg.Run.PrintResourcesUsage
   507  	trackResourcesEndCh := make(chan struct{})
   508  	defer func() { // XXX: this defer must be before ctx.cancel defer
   509  		if needTrackResources { // wait until resource tracking finished to print properly
   510  			<-trackResourcesEndCh
   511  		}
   512  	}()
   513  
   514  	e.setTimeoutToDeadlineIfOnlyDeadlineIsSet()
   515  	ctx, cancel := context.WithTimeout(context.Background(), e.cfg.Run.Timeout)
   516  	defer cancel()
   517  
   518  	if needTrackResources {
   519  		go watchResources(ctx, trackResourcesEndCh, e.log, e.debugf)
   520  	}
   521  
   522  	if err := e.runAndPrint(ctx, args); err != nil {
   523  		e.log.Errorf("Running error: %s", err)
   524  		if e.exitCode == exitcodes.Success {
   525  			if exitErr, ok := errors.Cause(err).(*exitcodes.ExitError); ok {
   526  				e.exitCode = exitErr.Code
   527  			} else {
   528  				e.exitCode = exitcodes.Failure
   529  			}
   530  		}
   531  	}
   532  
   533  	e.setupExitCode(ctx)
   534  }
   535  
   536  // to be removed when deadline is finally decommissioned
   537  func (e *Executor) setTimeoutToDeadlineIfOnlyDeadlineIsSet() {
   538  	deadlineValue := e.cfg.Run.Deadline
   539  	if deadlineValue != 0 && e.cfg.Run.Timeout == defaultTimeout {
   540  		e.cfg.Run.Timeout = deadlineValue
   541  	}
   542  }
   543  
   544  func (e *Executor) setupExitCode(ctx context.Context) {
   545  	if ctx.Err() != nil {
   546  		e.exitCode = exitcodes.Timeout
   547  		e.log.Errorf("Timeout exceeded: try increasing it by passing --timeout option")
   548  		return
   549  	}
   550  
   551  	if e.exitCode != exitcodes.Success {
   552  		return
   553  	}
   554  
   555  	needFailOnWarnings := os.Getenv(lintersdb.EnvTestRun) == "1" || os.Getenv(envFailOnWarnings) == "1"
   556  	if needFailOnWarnings && len(e.reportData.Warnings) != 0 {
   557  		e.exitCode = exitcodes.WarningInTest
   558  		return
   559  	}
   560  
   561  	if e.reportData.Error != "" {
   562  		// it's a case e.g. when typecheck linter couldn't parse and error and just logged it
   563  		e.exitCode = exitcodes.ErrorWasLogged
   564  		return
   565  	}
   566  }
   567  
   568  func watchResources(ctx context.Context, done chan struct{}, logger logutils.Log, debugf logutils.DebugFunc) {
   569  	startedAt := time.Now()
   570  	debugf("Started tracking time")
   571  
   572  	var maxRSSMB, totalRSSMB float64
   573  	var iterationsCount int
   574  
   575  	const intervalMS = 100
   576  	ticker := time.NewTicker(intervalMS * time.Millisecond)
   577  	defer ticker.Stop()
   578  
   579  	logEveryRecord := os.Getenv(envMemLogEvery) == "1"
   580  	const MB = 1024 * 1024
   581  
   582  	track := func() {
   583  		var m runtime.MemStats
   584  		runtime.ReadMemStats(&m)
   585  
   586  		if logEveryRecord {
   587  			debugf("Stopping memory tracing iteration, printing ...")
   588  			printMemStats(&m, logger)
   589  		}
   590  
   591  		rssMB := float64(m.Sys) / MB
   592  		if rssMB > maxRSSMB {
   593  			maxRSSMB = rssMB
   594  		}
   595  		totalRSSMB += rssMB
   596  		iterationsCount++
   597  	}
   598  
   599  	for {
   600  		track()
   601  
   602  		stop := false
   603  		select {
   604  		case <-ctx.Done():
   605  			stop = true
   606  			debugf("Stopped resources tracking")
   607  		case <-ticker.C:
   608  		}
   609  
   610  		if stop {
   611  			break
   612  		}
   613  	}
   614  	track()
   615  
   616  	avgRSSMB := totalRSSMB / float64(iterationsCount)
   617  
   618  	logger.Infof("Memory: %d samples, avg is %.1fMB, max is %.1fMB",
   619  		iterationsCount, avgRSSMB, maxRSSMB)
   620  	logger.Infof("Execution took %s", time.Since(startedAt))
   621  	close(done)
   622  }