@@ -26,20 +26,26 @@ import (
2626 "errors"
2727 "fmt"
2828 "io"
29+ "io/ioutil"
2930 "log"
3031 "net/http"
3132 "os"
3233 "path/filepath"
34+ "strings"
35+ "text/template"
3336
3437 "github.com/magefile/mage/mg"
3538 "github.com/magefile/mage/sh"
3639)
3740
3841const (
39- linterInstallURL = "https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
40- linterInstallFilename = "./build/intall-golang-ci.sh"
41- linterBinaryFilename = "./build/golangci-lint"
42- linterVersion = "v1.44.0"
42+ linterInstallURL = "https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
43+ linterInstallFilename = "./build/intall-golang-ci.sh"
44+ linterBinaryFilename = "./build/golangci-lint"
45+ linterVersion = "v1.44.0"
46+ linterConfigFilename = "./.golangci.yml"
47+ linterConfigTemplateFilename = "./dev-tools/templates/.golangci.yml"
48+ goVersionFilename = "./.go-version"
4349)
4450
4551// Aliases are shortcuts to long target names.
@@ -52,6 +58,57 @@ var Aliases = map[string]interface{}{
5258// Linter contains targets related to linting the Go code
5359type Linter mg.Namespace
5460
61+ // UpdateGoVersion updates the linter configuration with the new version of Go.
62+ func (Linter ) UpdateGoVersion () error {
63+ goVersionBytes , err := ioutil .ReadFile (goVersionFilename )
64+ if err != nil {
65+ return fmt .Errorf ("failed to read the %q file: %w" , goVersionFilename , err )
66+ }
67+ goVersion := strings .TrimSpace (string (goVersionBytes ))
68+ log .Printf ("The Go version is %s\n " , goVersion )
69+
70+ templateContext := struct { GoVersion string }{GoVersion : goVersion }
71+ template , err := template .ParseFiles (linterConfigTemplateFilename )
72+ if err != nil {
73+ return fmt .Errorf ("failed to read the template file %q: %w" , linterConfigTemplateFilename , err )
74+ }
75+
76+ configFile , err := os .OpenFile (linterConfigFilename , os .O_TRUNC | os .O_CREATE | os .O_WRONLY , 0700 )
77+ if err != nil {
78+ return fmt .Errorf ("failed to create/replace the linter config %q: %w" , linterConfigFilename , err )
79+ }
80+ defer configFile .Close ()
81+
82+ warning := fmt .Sprintf ("# DO NOT EDIT!\n # This file is a rendered template, the source can be found in %q\n #\n " , linterConfigTemplateFilename )
83+ _ , err = configFile .WriteString (warning )
84+ if err != nil {
85+ return fmt .Errorf ("failed to write into the linter config %q: %w" , linterConfigFilename , err )
86+ }
87+
88+ err = template .Execute (configFile , templateContext )
89+ if err != nil {
90+ return fmt .Errorf ("failed to execute the template %q: %w" , linterConfigTemplateFilename , err )
91+ }
92+
93+ err = assertUnchanged (linterConfigFilename )
94+ if err != nil {
95+ log .Printf ("Successfully updated the linter configuration %q to Go version %s, please commit the changes now" , linterConfigFilename , goVersion )
96+ } else {
97+ log .Printf ("The linter configuration %q is up to date, no changes made" , linterConfigFilename )
98+ }
99+
100+ return nil
101+ }
102+
103+ // CheckConfig makes sure that the `.golangci.yml` does not have uncommitted changes
104+ func (Linter ) CheckConfig () error {
105+ err := assertUnchanged (linterConfigFilename )
106+ if err != nil {
107+ return fmt .Errorf ("linter configuration has uncommitted changes: %w" , err )
108+ }
109+ return nil
110+ }
111+
55112// Install installs golangci-lint (https://golangci-lint.run) to `./build`
56113// using the official installation script downloaded from GitHub.
57114// If the linter binary already exists does nothing.
@@ -64,22 +121,22 @@ func (Linter) Install() error {
64121
65122 _ , err = os .Stat (linterBinaryFilename )
66123 if err == nil {
67- log .Println ("the linter has been already installed, skipping..." )
124+ log .Println ("The linter has been already installed, skipping..." )
68125 return nil
69126 }
70127 if ! errors .Is (err , os .ErrNotExist ) {
71128 return fmt .Errorf ("failed check if file %q exists: %w" , linterBinaryFilename , err )
72129 }
73130
74- log .Println ("preparing the installation script file..." )
131+ log .Println ("Preparing the installation script file..." )
75132
76133 installScript , err := os .OpenFile (linterInstallFilename , os .O_TRUNC | os .O_CREATE | os .O_WRONLY , 0700 )
77134 if err != nil {
78135 return fmt .Errorf ("failed to create file %q: %w" , linterInstallFilename , err )
79136 }
80137 defer installScript .Close ()
81138
82- log .Println ("downloading the linter installation script..." )
139+ log .Println ("Downloading the linter installation script..." )
83140 // nolint: noctx // valid use since there is no context
84141 resp , err := http .Get (linterInstallURL )
85142 if err != nil {
@@ -110,14 +167,14 @@ func (Linter) Install() error {
110167
111168// All runs the linter against the entire codebase
112169func (l Linter ) All () error {
113- mg .Deps (l .Install )
170+ mg .Deps (l .Install , l . CheckConfig )
114171 return runLinter ()
115172}
116173
117174// LastChange runs the linter against all files changed since the fork point from `main`.
118175// If the current branch is `main` then runs against the files changed in the last commit.
119176func (l Linter ) LastChange () error {
120- mg .Deps (l .Install )
177+ mg .Deps (l .Install , l . CheckConfig )
121178
122179 branch , err := sh .Output ("git" , "branch" , "--show-current" )
123180 if err != nil {
@@ -146,6 +203,13 @@ func Check() error {
146203 return nil
147204}
148205
206+ // UpdateGoVersion makes required changes in order to switch to a new version of Go set in `./.go-version`.
207+ // nolint: deadcode,unparam // it's used as a `mage` target and requires returning an error
208+ func UpdateGoVersion () error {
209+ mg .Deps (Linter .UpdateGoVersion )
210+ return nil
211+ }
212+
149213// Deps contains targets related to checking dependencies
150214type Deps mg.Namespace
151215
@@ -182,14 +246,23 @@ func (Deps) CheckModuleTidy() error {
182246 if err != nil {
183247 return err
184248 }
185- err = sh . Run ( "git" , "diff" , "--exit-code" , "go.mod" )
249+ err = assertUnchanged ( "go.mod" )
186250 if err != nil {
187251 return fmt .Errorf ("`go mod tidy` was not called before the last commit: %w" , err )
188252 }
189253
190254 return nil
191255}
192256
257+ func assertUnchanged (path string ) error {
258+ err := sh .Run ("git" , "diff" , "--exit-code" , path )
259+ if err != nil {
260+ return fmt .Errorf ("failed to assert the unchanged file %q: %w" , path , err )
261+ }
262+
263+ return nil
264+ }
265+
193266// runWithStdErr runs a command redirecting its stderr to the console instead of discarding it
194267func runWithStdErr (command string , args ... string ) error {
195268 _ , err := sh .Exec (nil , os .Stdout , os .Stderr , command , args ... )
@@ -207,6 +280,7 @@ func runLinter(runFlags ...string) error {
207280
208281 args = append (args , "run" )
209282 args = append (args , runFlags ... )
283+ args = append (args , "-c" , linterConfigFilename )
210284 args = append (args , "./..." )
211285
212286 return runWithStdErr (linterBinaryFilename , args ... )
0 commit comments