github.com/elek/golangci-lint@v1.42.2-0.20211208090441-c05b7fcb3a9a/pkg/commands/executor.go (about) 1 package commands 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/sha256" 7 "io" 8 "os" 9 "path/filepath" 10 "strings" 11 "time" 12 13 "github.com/fatih/color" 14 "github.com/gofrs/flock" 15 "github.com/pkg/errors" 16 "github.com/spf13/cobra" 17 "github.com/spf13/pflag" 18 "gopkg.in/yaml.v3" 19 20 "github.com/elek/golangci-lint/internal/cache" 21 "github.com/elek/golangci-lint/internal/pkgcache" 22 "github.com/elek/golangci-lint/pkg/config" 23 "github.com/elek/golangci-lint/pkg/fsutils" 24 "github.com/elek/golangci-lint/pkg/golinters/goanalysis/load" 25 "github.com/elek/golangci-lint/pkg/goutil" 26 "github.com/elek/golangci-lint/pkg/lint" 27 "github.com/elek/golangci-lint/pkg/lint/lintersdb" 28 "github.com/elek/golangci-lint/pkg/logutils" 29 "github.com/elek/golangci-lint/pkg/report" 30 "github.com/elek/golangci-lint/pkg/timeutils" 31 ) 32 33 type Executor struct { 34 rootCmd *cobra.Command 35 runCmd *cobra.Command 36 lintersCmd *cobra.Command 37 38 exitCode int 39 version, commit, date string 40 41 cfg *config.Config 42 log logutils.Log 43 reportData report.Data 44 DBManager *lintersdb.Manager 45 EnabledLintersSet *lintersdb.EnabledSet 46 contextLoader *lint.ContextLoader 47 goenv *goutil.Env 48 fileCache *fsutils.FileCache 49 lineCache *fsutils.LineCache 50 pkgCache *pkgcache.Cache 51 debugf logutils.DebugFunc 52 sw *timeutils.Stopwatch 53 54 loadGuard *load.Guard 55 flock *flock.Flock 56 } 57 58 func NewExecutor(version, commit, date string) *Executor { 59 startedAt := time.Now() 60 e := &Executor{ 61 cfg: config.NewDefault(), 62 version: version, 63 commit: commit, 64 date: date, 65 DBManager: lintersdb.NewManager(nil, nil), 66 debugf: logutils.Debug("exec"), 67 } 68 69 e.debugf("Starting execution...") 70 e.log = report.NewLogWrapper(logutils.NewStderrLog(""), &e.reportData) 71 72 // to setup log level early we need to parse config from command line extra time to 73 // find `-v` option 74 commandLineCfg, err := e.getConfigForCommandLine() 75 if err != nil && err != pflag.ErrHelp { 76 e.log.Fatalf("Can't get config for command line: %s", err) 77 } 78 if commandLineCfg != nil { 79 logutils.SetupVerboseLog(e.log, commandLineCfg.Run.IsVerbose) 80 81 switch commandLineCfg.Output.Color { 82 case "always": 83 color.NoColor = false 84 case "never": 85 color.NoColor = true 86 case "auto": 87 // nothing 88 default: 89 e.log.Fatalf("invalid value %q for --color; must be 'always', 'auto', or 'never'", commandLineCfg.Output.Color) 90 } 91 } 92 93 // init of commands must be done before config file reading because 94 // init sets config with the default values of flags 95 e.initRoot() 96 e.initRun() 97 e.initHelp() 98 e.initLinters() 99 e.initConfig() 100 e.initVersion() 101 e.initCache() 102 103 // init e.cfg by values from config: flags parse will see these values 104 // like the default ones. It will overwrite them only if the same option 105 // is found in command-line: it's ok, command-line has higher priority. 106 107 r := config.NewFileReader(e.cfg, commandLineCfg, e.log.Child("config_reader")) 108 if err = r.Read(); err != nil { 109 e.log.Fatalf("Can't read config: %s", err) 110 } 111 112 // recreate after getting config 113 e.DBManager = lintersdb.NewManager(e.cfg, e.log).WithCustomLinters() 114 115 e.cfg.LintersSettings.Gocritic.InferEnabledChecks(e.log) 116 if err = e.cfg.LintersSettings.Gocritic.Validate(e.log); err != nil { 117 e.log.Fatalf("Invalid gocritic settings: %s", err) 118 } 119 120 // Slice options must be explicitly set for proper merging of config and command-line options. 121 fixSlicesFlags(e.runCmd.Flags()) 122 fixSlicesFlags(e.lintersCmd.Flags()) 123 124 e.EnabledLintersSet = lintersdb.NewEnabledSet(e.DBManager, 125 lintersdb.NewValidator(e.DBManager), e.log.Child("lintersdb"), e.cfg) 126 e.goenv = goutil.NewEnv(e.log.Child("goenv")) 127 e.fileCache = fsutils.NewFileCache() 128 e.lineCache = fsutils.NewLineCache(e.fileCache) 129 130 e.sw = timeutils.NewStopwatch("pkgcache", e.log.Child("stopwatch")) 131 e.pkgCache, err = pkgcache.NewCache(e.sw, e.log.Child("pkgcache")) 132 if err != nil { 133 e.log.Fatalf("Failed to build packages cache: %s", err) 134 } 135 e.loadGuard = load.NewGuard() 136 e.contextLoader = lint.NewContextLoader(e.cfg, e.log.Child("loader"), e.goenv, 137 e.lineCache, e.fileCache, e.pkgCache, e.loadGuard) 138 if err = e.initHashSalt(version); err != nil { 139 e.log.Fatalf("Failed to init hash salt: %s", err) 140 } 141 e.debugf("Initialized executor in %s", time.Since(startedAt)) 142 return e 143 } 144 145 func (e *Executor) Execute() error { 146 return e.rootCmd.Execute() 147 } 148 149 func (e *Executor) initHashSalt(version string) error { 150 binSalt, err := computeBinarySalt(version) 151 if err != nil { 152 return errors.Wrap(err, "failed to calculate binary salt") 153 } 154 155 configSalt, err := computeConfigSalt(e.cfg) 156 if err != nil { 157 return errors.Wrap(err, "failed to calculate config salt") 158 } 159 160 var b bytes.Buffer 161 b.Write(binSalt) 162 b.Write(configSalt) 163 cache.SetSalt(b.Bytes()) 164 return nil 165 } 166 167 func computeBinarySalt(version string) ([]byte, error) { 168 if version != "" && version != "(devel)" { 169 return []byte(version), nil 170 } 171 172 if logutils.HaveDebugTag("bin_salt") { 173 return []byte("debug"), nil 174 } 175 176 p, err := os.Executable() 177 if err != nil { 178 return nil, err 179 } 180 f, err := os.Open(p) 181 if err != nil { 182 return nil, err 183 } 184 defer f.Close() 185 h := sha256.New() 186 if _, err := io.Copy(h, f); err != nil { 187 return nil, err 188 } 189 return h.Sum(nil), nil 190 } 191 192 func computeConfigSalt(cfg *config.Config) ([]byte, error) { 193 // We don't hash all config fields to reduce meaningless cache 194 // invalidations. At least, it has a huge impact on tests speed. 195 196 lintersSettingsBytes, err := yaml.Marshal(cfg.LintersSettings) 197 if err != nil { 198 return nil, errors.Wrap(err, "failed to json marshal config linter settings") 199 } 200 201 var configData bytes.Buffer 202 configData.WriteString("linters-settings=") 203 configData.Write(lintersSettingsBytes) 204 configData.WriteString("\nbuild-tags=%s" + strings.Join(cfg.Run.BuildTags, ",")) 205 206 h := sha256.New() 207 if _, err := h.Write(configData.Bytes()); err != nil { 208 return nil, err 209 } 210 return h.Sum(nil), nil 211 } 212 213 func (e *Executor) acquireFileLock() bool { 214 if e.cfg.Run.AllowParallelRunners { 215 e.debugf("Parallel runners are allowed, no locking") 216 return true 217 } 218 219 lockFile := filepath.Join(os.TempDir(), "golangci-lint.lock") 220 e.debugf("Locking on file %s...", lockFile) 221 f := flock.New(lockFile) 222 const retryDelay = time.Second 223 224 ctx := context.Background() 225 if !e.cfg.Run.AllowSerialRunners { 226 const totalTimeout = 5 * time.Second 227 var cancel context.CancelFunc 228 ctx, cancel = context.WithTimeout(ctx, totalTimeout) 229 defer cancel() 230 } 231 if ok, _ := f.TryLockContext(ctx, retryDelay); !ok { 232 return false 233 } 234 235 e.flock = f 236 return true 237 } 238 239 func (e *Executor) releaseFileLock() { 240 if e.cfg.Run.AllowParallelRunners { 241 return 242 } 243 244 if err := e.flock.Unlock(); err != nil { 245 e.debugf("Failed to unlock on file: %s", err) 246 } 247 if err := os.Remove(e.flock.Path()); err != nil { 248 e.debugf("Failed to remove lock file: %s", err) 249 } 250 } 251 252 func (e *Executor) ExitCode() int { 253 return e.exitCode 254 }