@@ -36,81 +36,137 @@ import (
3636)
3737
3838const (
39- linterInstallFilename = "./intall-golang-ci.sh"
40- linterBinaryFilename = "./bin/golangci-lint"
39+ linterInstallURL = "https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
40+ linterInstallFilename = "./build/intall-golang-ci.sh"
41+ linterBinaryFilename = "./build/golangci-lint"
4142 linterVersion = "v1.44.0"
4243)
4344
44- // InstallLinter installs golangci-lint (https://golangci-lint.run) to `./bin`
45+ // Aliases are shortcuts to long target names.
46+ // nolint: deadcode // it's used by `mage`.
47+ var Aliases = map [string ]interface {}{
48+ "llc" : Linter .LastChange ,
49+ "lint" : Linter .All ,
50+ }
51+
52+ // Linter contains targets related to linting the Go code
53+ type Linter mg.Namespace
54+
55+ // Install installs golangci-lint (https://golangci-lint.run) to `./build`
4556// using the official installation script downloaded from GitHub.
4657// If the linter binary already exists does nothing.
47- func InstallLinter () error {
48- _ , err := os .Stat (linterBinaryFilename )
58+ func (Linter ) Install () error {
59+ dirPath := filepath .Dir (linterBinaryFilename )
60+ err := os .MkdirAll (dirPath , 0700 )
61+ if err != nil {
62+ return fmt .Errorf ("failed to create path %q: %w" , dirPath , err )
63+ }
64+
65+ _ , err = os .Stat (linterBinaryFilename )
4966 if err == nil {
50- log .Println ("already installed, exiting ..." )
67+ log .Println ("the linter has been already installed, skipping ..." )
5168 return nil
5269 }
5370 if ! errors .Is (err , os .ErrNotExist ) {
54- return err
71+ return fmt . Errorf ( "failed check if file %q exists: %w" , linterBinaryFilename , err )
5572 }
5673
5774 log .Println ("preparing the installation script file..." )
75+
5876 installScript , err := os .OpenFile (linterInstallFilename , os .O_TRUNC | os .O_CREATE | os .O_WRONLY , 0700 )
5977 if err != nil {
60- return err
78+ return fmt . Errorf ( "failed to create file %q: %w" , linterInstallFilename , err )
6179 }
6280 defer installScript .Close ()
6381
6482 log .Println ("downloading the linter installation script..." )
65- resp , err := http .Get ("https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh" )
83+ // nolint: noctx // valid use since there is no context
84+ resp , err := http .Get (linterInstallURL )
6685 if err != nil {
67- return err
86+ return fmt . Errorf ( "cannot download the linter installation script from %q: %w" , linterInstallURL , err )
6887 }
6988 defer resp .Body .Close ()
7089
71- lr := io .LimitReader (resp .Body , 1024 * 100 ) // not more than 100 KB
90+ lr := io .LimitReader (resp .Body , 1024 * 100 ) // not more than 100 KB, just to be safe
7291 _ , err = io .Copy (installScript , lr )
7392 if err != nil {
74- return err
93+ return fmt . Errorf ( "failed to finish downloading the linter installation script: %w" , err )
7594 }
7695
7796 err = installScript .Close () // otherwise we cannot run the script
7897 if err != nil {
79- return err
98+ return fmt .Errorf ("failed to close file %q: %w" , linterInstallFilename , err )
99+ }
100+
101+ binaryDir := filepath .Dir (linterBinaryFilename )
102+ err = os .MkdirAll (binaryDir , 0700 )
103+ if err != nil {
104+ return fmt .Errorf ("cannot create path %q: %w" , binaryDir , err )
80105 }
81106
82- return sh .Run (linterInstallFilename , linterVersion )
107+ // there must be no space after `-b`, otherwise the script does not work correctly ¯\_(ツ)_/¯
108+ return sh .Run (linterInstallFilename , "-b" + binaryDir , linterVersion )
83109}
84110
85- // LintAll runs the linter against the entire codebase
86- func LintAll () error {
87- mg .Deps (InstallLinter )
88- return sh . Run ( linterBinaryFilename , "-v" , "run" , "./..." )
111+ // All runs the linter against the entire codebase
112+ func ( l Linter ) All () error {
113+ mg .Deps (l . Install )
114+ return runLinter ( )
89115}
90116
117+ // LastChange runs the linter against all files changed since the fork point from `main`.
118+ // If the current branch is `main` then runs against the files changed in the last commit.
119+ func (l Linter ) LastChange () error {
120+ mg .Deps (l .Install )
121+
122+ branch , err := sh .Output ("git" , "branch" , "--show-current" )
123+ if err != nil {
124+ return fmt .Errorf ("failed to get the current branch: %w" , err )
125+ }
126+
127+ // the linter is supposed to support linting changed diffs only but,
128+ // for some reason, it simply does not work - does not output any
129+ // results without linting the whole files, so we have to use `--whole-files`
130+ // which can lead to some frustration from developers who would like to
131+ // fix a single line in an existing codebase and the linter would force them
132+ // into fixing all linting issues in the whole file instead
133+
134+ if branch == "main" {
135+ // files changed in the last commit
136+ return runLinter ("--new-from-rev=HEAD~" , "--whole-files" )
137+ }
138+
139+ return runLinter ("--new-from-rev=origin/main" , "--whole-files" )
140+ }
141+
142+ // Check runs all the checks
143+ // nolint: deadcode,unparam // it's used as a `mage` target and requires returning an error
91144func Check () error {
92- mg .Deps (CheckNoBeatsDependency , CheckModuleTidy , LintAll )
145+ mg .Deps (Deps . CheckNoBeats , Deps . CheckModuleTidy , Linter . LastChange )
93146 return nil
94147}
95148
96- // CheckNoBeatsDependency is required to make sure we are not introducing
149+ // Deps contains targets related to checking dependencies
150+ type Deps mg.Namespace
151+
152+ // CheckNoBeats is required to make sure we are not introducing
97153// dependency on elastic/beats.
98- func CheckNoBeatsDependency () error {
154+ func ( Deps ) CheckNoBeats () error {
99155 goModPath , err := filepath .Abs ("go.mod" )
100156 if err != nil {
101157 return err
102158 }
103159 goModFile , err := os .Open (goModPath )
104160 if err != nil {
105- return fmt .Errorf ("failed to open module file: %+v " , err )
161+ return fmt .Errorf ("failed to open module file: %w " , err )
106162 }
107163 beatsImport := []byte ("github.com/elastic/beats" )
108164 scanner := bufio .NewScanner (goModFile )
109165 lineCount := 1
110166 for scanner .Scan () {
111167 line := scanner .Bytes ()
112168 if bytes .Contains (line , beatsImport ) {
113- return fmt .Errorf ("line %d is beats dependency: '%s'\n Please, make sure you are not adding anything that depends on %s" , lineCount , line , beatsImport )
169+ return fmt .Errorf ("line %d is a beats dependency: '%s'\n Please, make sure you are not adding anything that depends on %s" , lineCount , line , beatsImport )
114170 }
115171 lineCount ++
116172 }
@@ -120,16 +176,38 @@ func CheckNoBeatsDependency() error {
120176 return nil
121177}
122178
123- // CheckModuleTidy checks if `go mod tidy` was run before.
124- func CheckModuleTidy () error {
179+ // CheckModuleTidy checks if `go mod tidy` was run before the last commit .
180+ func ( Deps ) CheckModuleTidy () error {
125181 err := sh .Run ("go" , "mod" , "tidy" )
126182 if err != nil {
127183 return err
128184 }
129- err = sh .Run ("git" , "diff" , "--exit-code" )
185+ err = sh .Run ("git" , "diff" , "--exit-code" , "go.mod" )
130186 if err != nil {
131- return fmt .Errorf ("`go mod tidy` was not called before committing : %w" , err )
187+ return fmt .Errorf ("`go mod tidy` was not called before the last commit : %w" , err )
132188 }
133189
134190 return nil
135191}
192+
193+ // runWithStdErr runs a command redirecting its stderr to the console instead of discarding it
194+ func runWithStdErr (command string , args ... string ) error {
195+ _ , err := sh .Exec (nil , os .Stdout , os .Stderr , command , args ... )
196+ return err
197+ }
198+
199+ // runLinter runs the linter passing the `mage -v` (verbose mode) and given arguments.
200+ // Also redirects linter's output to the `stderr` instead of discarding it.
201+ func runLinter (runFlags ... string ) error {
202+ var args []string
203+
204+ if mg .Verbose () {
205+ args = append (args , "-v" )
206+ }
207+
208+ args = append (args , "run" )
209+ args = append (args , runFlags ... )
210+ args = append (args , "./..." )
211+
212+ return runWithStdErr (linterBinaryFilename , args ... )
213+ }
0 commit comments