Skip to content

Commit fd72cf7

Browse files
authored
Reorganise the magefile (#11)
- Added target namespaces - Added mage target to lint changes since the `main` branch or a last commit on the `main` branch - Addressed linting issues in the magefile. By default, it's excluded because of the build tags. - Fixed the `go mod tidy` check to make it look for a diff in `go.mod` only
1 parent cb28916 commit fd72cf7

File tree

3 files changed

+116
-28
lines changed

3 files changed

+116
-28
lines changed

.ci/Jenkinsfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pipeline {
3737
withGithubNotify(context: "Lint") {
3838
dir("${BASE_DIR}"){
3939
withMageEnv(){
40-
sh(label: 'Mage check', script: 'mage check')
40+
sh(label: 'Mage check', script: 'mage -v check')
4141
}
4242
}
4343
}

.golangci.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@ run:
33
# timeout for analysis, e.g. 30s, 5m, default is 1m
44
timeout: 1m
55

6+
issues:
7+
# Maximum count of issues with the same text.
8+
# Set to 0 to disable.
9+
# Default: 3
10+
max-same-issues: 0
11+
# Maximum issues count per one linter.
12+
# Set to 0 to disable.
13+
# Default: 50
14+
max-issues-per-linter: 0
15+
616
output:
717
sort-results: true
818

magefile.go

Lines changed: 105 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -36,81 +36,137 @@ import (
3636
)
3737

3838
const (
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
91144
func 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'\nPlease, 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'\nPlease, 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

Comments
 (0)