Skip to content

Commit 784264d

Browse files
authored
dev: new commands system (#4412)
1 parent b5d7302 commit 784264d

File tree

20 files changed

+1180
-1144
lines changed

20 files changed

+1180
-1144
lines changed

cmd/golangci-lint/main.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,8 @@ func main() {
3636
Date: date,
3737
}
3838

39-
e := commands.NewExecutor(info)
40-
41-
if err := e.Execute(); err != nil {
42-
fmt.Fprintf(os.Stderr, "failed executing command with error %v\n", err)
39+
if err := commands.Execute(info); err != nil {
40+
_, _ = fmt.Fprintf(os.Stderr, "failed executing command with error %v\n", err)
4341
os.Exit(exitcodes.Failure)
4442
}
4543
}

docs/src/docs/contributing/architecture.mdx

-84
Original file line numberDiff line numberDiff line change
@@ -22,90 +22,6 @@ graph LR
2222

2323
</ResponsiveContainer>
2424

25-
## Init
26-
27-
The execution starts here:
28-
29-
```go title=cmd/golangci-lint/main.go
30-
func main() {
31-
e := commands.NewExecutor(info)
32-
33-
if err := e.Execute(); err != nil {
34-
fmt.Fprintf(os.Stderr, "failed executing command with error %v\n", err)
35-
os.Exit(exitcodes.Failure)
36-
}
37-
}
38-
```
39-
40-
The **executer** is our abstraction:
41-
42-
```go title=pkg/commands/executor.go
43-
type Executor struct {
44-
rootCmd *cobra.Command
45-
runCmd *cobra.Command
46-
lintersCmd *cobra.Command
47-
48-
exitCode int
49-
buildInfo BuildInfo
50-
51-
cfg *config.Config
52-
log logutils.Log
53-
reportData report.Data
54-
DBManager *lintersdb.Manager
55-
EnabledLintersSet *lintersdb.EnabledSet
56-
contextLoader *lint.ContextLoader
57-
goenv *goutil.Env
58-
fileCache *fsutils.FileCache
59-
lineCache *fsutils.LineCache
60-
pkgCache *pkgcache.Cache
61-
debugf logutils.DebugFunc
62-
sw *timeutils.Stopwatch
63-
64-
loadGuard *load.Guard
65-
flock *flock.Flock
66-
}
67-
```
68-
69-
We use dependency injection and all root dependencies are stored in this executor.
70-
71-
In the function `NewExecutor` we do the following:
72-
73-
1. Initialize dependencies.
74-
2. Initialize [cobra](https://github.com/spf13/cobra) commands.
75-
3. Parse the config file using [viper](https://github.com/spf13/viper) and merge it with command line arguments.
76-
77-
The following execution is controlled by `cobra`. If a user executes `golangci-lint run`
78-
then `cobra` executes `e.runCmd`.
79-
80-
Different `cobra` commands have different runners, e.g. a `run` command is configured in the following way:
81-
82-
```go title=pkg/commands/run.go
83-
func (e *Executor) initRun() {
84-
e.runCmd = &cobra.Command{
85-
Use: "run",
86-
Short: "Run the linters",
87-
Run: e.executeRun,
88-
PreRunE: func(_ *cobra.Command, _ []string) error {
89-
if ok := e.acquireFileLock(); !ok {
90-
return errors.New("parallel golangci-lint is running")
91-
}
92-
return nil
93-
},
94-
PostRun: func(_ *cobra.Command, _ []string) {
95-
e.releaseFileLock()
96-
},
97-
}
98-
e.rootCmd.AddCommand(e.runCmd)
99-
100-
e.runCmd.SetOut(logutils.StdOut) // use custom output to properly color it in Windows terminals
101-
e.runCmd.SetErr(logutils.StdErr)
102-
103-
e.initRunConfiguration(e.runCmd)
104-
}
105-
```
106-
107-
The primary execution function of the `run` command is `executeRun`.
108-
10925
## Load Packages
11026

11127
Loading packages is listing all packages and their recursive dependencies for analysis.

pkg/commands/cache.go

+30-92
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
package commands
22

33
import (
4-
"bytes"
5-
"crypto/sha256"
64
"fmt"
7-
"io"
85
"os"
96
"path/filepath"
10-
"strings"
117

128
"github.com/spf13/cobra"
13-
"gopkg.in/yaml.v3"
149

1510
"github.com/golangci/golangci-lint/internal/cache"
16-
"github.com/golangci/golangci-lint/pkg/config"
1711
"github.com/golangci/golangci-lint/pkg/fsutils"
1812
"github.com/golangci/golangci-lint/pkg/logutils"
1913
)
2014

21-
func (e *Executor) initCache() {
15+
type cacheCommand struct {
16+
cmd *cobra.Command
17+
}
18+
19+
func newCacheCommand() *cacheCommand {
20+
c := &cacheCommand{}
21+
2222
cacheCmd := &cobra.Command{
2323
Use: "cache",
2424
Short: "Cache control and information",
@@ -28,42 +28,45 @@ func (e *Executor) initCache() {
2828
},
2929
}
3030

31-
cacheCmd.AddCommand(&cobra.Command{
32-
Use: "clean",
33-
Short: "Clean cache",
34-
Args: cobra.NoArgs,
35-
ValidArgsFunction: cobra.NoFileCompletions,
36-
RunE: e.executeCacheClean,
37-
})
38-
cacheCmd.AddCommand(&cobra.Command{
39-
Use: "status",
40-
Short: "Show cache status",
41-
Args: cobra.NoArgs,
42-
ValidArgsFunction: cobra.NoFileCompletions,
43-
Run: e.executeCacheStatus,
44-
})
31+
cacheCmd.AddCommand(
32+
&cobra.Command{
33+
Use: "clean",
34+
Short: "Clean cache",
35+
Args: cobra.NoArgs,
36+
ValidArgsFunction: cobra.NoFileCompletions,
37+
RunE: c.executeClean,
38+
},
39+
&cobra.Command{
40+
Use: "status",
41+
Short: "Show cache status",
42+
Args: cobra.NoArgs,
43+
ValidArgsFunction: cobra.NoFileCompletions,
44+
Run: c.executeStatus,
45+
},
46+
)
4547

46-
// TODO: add trim command?
48+
c.cmd = cacheCmd
4749

48-
e.rootCmd.AddCommand(cacheCmd)
50+
return c
4951
}
5052

51-
func (e *Executor) executeCacheClean(_ *cobra.Command, _ []string) error {
53+
func (c *cacheCommand) executeClean(_ *cobra.Command, _ []string) error {
5254
cacheDir := cache.DefaultDir()
55+
5356
if err := os.RemoveAll(cacheDir); err != nil {
5457
return fmt.Errorf("failed to remove dir %s: %w", cacheDir, err)
5558
}
5659

5760
return nil
5861
}
5962

60-
func (e *Executor) executeCacheStatus(_ *cobra.Command, _ []string) {
63+
func (c *cacheCommand) executeStatus(_ *cobra.Command, _ []string) {
6164
cacheDir := cache.DefaultDir()
62-
fmt.Fprintf(logutils.StdOut, "Dir: %s\n", cacheDir)
65+
_, _ = fmt.Fprintf(logutils.StdOut, "Dir: %s\n", cacheDir)
6366

6467
cacheSizeBytes, err := dirSizeBytes(cacheDir)
6568
if err == nil {
66-
fmt.Fprintf(logutils.StdOut, "Size: %s\n", fsutils.PrettifyBytesCount(cacheSizeBytes))
69+
_, _ = fmt.Fprintf(logutils.StdOut, "Size: %s\n", fsutils.PrettifyBytesCount(cacheSizeBytes))
6770
}
6871
}
6972

@@ -77,68 +80,3 @@ func dirSizeBytes(path string) (int64, error) {
7780
})
7881
return size, err
7982
}
80-
81-
// --- Related to cache but not used directly by the cache command.
82-
83-
func initHashSalt(version string, cfg *config.Config) error {
84-
binSalt, err := computeBinarySalt(version)
85-
if err != nil {
86-
return fmt.Errorf("failed to calculate binary salt: %w", err)
87-
}
88-
89-
configSalt, err := computeConfigSalt(cfg)
90-
if err != nil {
91-
return fmt.Errorf("failed to calculate config salt: %w", err)
92-
}
93-
94-
b := bytes.NewBuffer(binSalt)
95-
b.Write(configSalt)
96-
cache.SetSalt(b.Bytes())
97-
return nil
98-
}
99-
100-
func computeBinarySalt(version string) ([]byte, error) {
101-
if version != "" && version != "(devel)" {
102-
return []byte(version), nil
103-
}
104-
105-
if logutils.HaveDebugTag(logutils.DebugKeyBinSalt) {
106-
return []byte("debug"), nil
107-
}
108-
109-
p, err := os.Executable()
110-
if err != nil {
111-
return nil, err
112-
}
113-
f, err := os.Open(p)
114-
if err != nil {
115-
return nil, err
116-
}
117-
defer f.Close()
118-
h := sha256.New()
119-
if _, err := io.Copy(h, f); err != nil {
120-
return nil, err
121-
}
122-
return h.Sum(nil), nil
123-
}
124-
125-
// computeConfigSalt computes configuration hash.
126-
// We don't hash all config fields to reduce meaningless cache invalidations.
127-
// At least, it has a huge impact on tests speed.
128-
// Fields: `LintersSettings` and `Run.BuildTags`.
129-
func computeConfigSalt(cfg *config.Config) ([]byte, error) {
130-
lintersSettingsBytes, err := yaml.Marshal(cfg.LintersSettings)
131-
if err != nil {
132-
return nil, fmt.Errorf("failed to json marshal config linter settings: %w", err)
133-
}
134-
135-
configData := bytes.NewBufferString("linters-settings=")
136-
configData.Write(lintersSettingsBytes)
137-
configData.WriteString("\nbuild-tags=%s" + strings.Join(cfg.Run.BuildTags, ","))
138-
139-
h := sha256.New()
140-
if _, err := h.Write(configData.Bytes()); err != nil {
141-
return nil, err
142-
}
143-
return h.Sum(nil), nil
144-
}

pkg/commands/config.go

+44-26
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,27 @@ import (
55
"os"
66

77
"github.com/spf13/cobra"
8-
"github.com/spf13/pflag"
98
"github.com/spf13/viper"
109

1110
"github.com/golangci/golangci-lint/pkg/config"
1211
"github.com/golangci/golangci-lint/pkg/exitcodes"
1312
"github.com/golangci/golangci-lint/pkg/fsutils"
13+
"github.com/golangci/golangci-lint/pkg/logutils"
1414
)
1515

16-
func (e *Executor) initConfig() {
16+
type configCommand struct {
17+
viper *viper.Viper
18+
cmd *cobra.Command
19+
20+
log logutils.Log
21+
}
22+
23+
func newConfigCommand(log logutils.Log) *configCommand {
24+
c := &configCommand{
25+
viper: viper.New(),
26+
log: log,
27+
}
28+
1729
configCmd := &cobra.Command{
1830
Use: "config",
1931
Short: "Config file information",
@@ -23,25 +35,38 @@ func (e *Executor) initConfig() {
2335
},
2436
}
2537

26-
pathCmd := &cobra.Command{
27-
Use: "path",
28-
Short: "Print used config path",
29-
Args: cobra.NoArgs,
30-
ValidArgsFunction: cobra.NoFileCompletions,
31-
Run: e.executePath,
32-
}
38+
configCmd.AddCommand(
39+
&cobra.Command{
40+
Use: "path",
41+
Short: "Print used config path",
42+
Args: cobra.NoArgs,
43+
ValidArgsFunction: cobra.NoFileCompletions,
44+
Run: c.execute,
45+
PreRunE: c.preRunE,
46+
},
47+
)
3348

34-
fs := pathCmd.Flags()
35-
fs.SortFlags = false // sort them as they are defined here
49+
c.cmd = configCmd
3650

37-
configCmd.AddCommand(pathCmd)
38-
e.rootCmd.AddCommand(configCmd)
51+
return c
3952
}
4053

41-
func (e *Executor) executePath(_ *cobra.Command, _ []string) {
42-
usedConfigFile := e.getUsedConfig()
54+
func (c *configCommand) preRunE(cmd *cobra.Command, _ []string) error {
55+
// The command doesn't depend on the real configuration.
56+
// It only needs to know the path of the configuration file.
57+
loader := config.NewLoader(c.log.Child(logutils.DebugKeyConfigReader), c.viper, cmd.Flags(), config.LoaderOptions{}, config.NewDefault())
58+
59+
if err := loader.Load(); err != nil {
60+
return fmt.Errorf("can't load config: %w", err)
61+
}
62+
63+
return nil
64+
}
65+
66+
func (c *configCommand) execute(_ *cobra.Command, _ []string) {
67+
usedConfigFile := c.getUsedConfig()
4368
if usedConfigFile == "" {
44-
e.log.Warnf("No config file detected")
69+
c.log.Warnf("No config file detected")
4570
os.Exit(exitcodes.NoConfigFileDetected)
4671
}
4772

@@ -50,24 +75,17 @@ func (e *Executor) executePath(_ *cobra.Command, _ []string) {
5075

5176
// getUsedConfig returns the resolved path to the golangci config file,
5277
// or the empty string if no configuration could be found.
53-
func (e *Executor) getUsedConfig() string {
54-
usedConfigFile := viper.ConfigFileUsed()
78+
func (c *configCommand) getUsedConfig() string {
79+
usedConfigFile := c.viper.ConfigFileUsed()
5580
if usedConfigFile == "" {
5681
return ""
5782
}
5883

5984
prettyUsedConfigFile, err := fsutils.ShortestRelPath(usedConfigFile, "")
6085
if err != nil {
61-
e.log.Warnf("Can't pretty print config file path: %s", err)
86+
c.log.Warnf("Can't pretty print config file path: %s", err)
6287
return usedConfigFile
6388
}
6489

6590
return prettyUsedConfigFile
6691
}
67-
68-
// --- Related to config but not used directly by the config command.
69-
70-
func initConfigFileFlagSet(fs *pflag.FlagSet, cfg *config.Run) {
71-
fs.StringVarP(&cfg.Config, "config", "c", "", wh("Read config from file path `PATH`"))
72-
fs.BoolVar(&cfg.NoConfig, "no-config", false, wh("Don't read config file"))
73-
}

0 commit comments

Comments
 (0)