Skip to content

Commit 0717bf6

Browse files
committed
feat: add new command
1 parent 35d4b3c commit 0717bf6

File tree

5 files changed

+476
-0
lines changed

5 files changed

+476
-0
lines changed

pkg/commands/custom.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package commands
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"os"
8+
9+
"github.com/spf13/cobra"
10+
11+
"github.com/golangci/golangci-lint/pkg/commands/internal"
12+
"github.com/golangci/golangci-lint/pkg/logutils"
13+
)
14+
15+
const envKeepTempFiles = "MYGCL_KEEP_TEMP_FILES"
16+
17+
type customCommand struct {
18+
cmd *cobra.Command
19+
20+
cfg *internal.Configuration
21+
22+
log logutils.Log
23+
}
24+
25+
func newCustomCommand(logger logutils.Log) *customCommand {
26+
c := &customCommand{log: logger}
27+
28+
customCmd := &cobra.Command{
29+
Use: "custom",
30+
Short: "Build a version of golangci-lint with custom linters.",
31+
Args: cobra.NoArgs,
32+
PreRunE: c.preRunE,
33+
RunE: c.runE,
34+
}
35+
36+
c.cmd = customCmd
37+
38+
return c
39+
}
40+
41+
func (c *customCommand) preRunE(_ *cobra.Command, _ []string) error {
42+
cfg, err := internal.LoadConfiguration()
43+
if err != nil {
44+
return err
45+
}
46+
47+
err = cfg.Validate()
48+
if err != nil {
49+
return err
50+
}
51+
52+
c.cfg = cfg
53+
54+
return nil
55+
}
56+
57+
func (c *customCommand) runE(_ *cobra.Command, _ []string) error {
58+
ctx := context.Background()
59+
60+
tmp, err := os.MkdirTemp(os.TempDir(), "mygcl")
61+
if err != nil {
62+
return fmt.Errorf("create temporary directory: %w", err)
63+
}
64+
65+
defer func() {
66+
if os.Getenv(envKeepTempFiles) != "" {
67+
log.Printf("WARN: The env var %s has been dectected: the temporary directory is preserved: %s", envKeepTempFiles, tmp)
68+
69+
return
70+
}
71+
72+
_ = os.RemoveAll(tmp)
73+
}()
74+
75+
err = internal.NewBuilder(c.log, c.cfg, tmp).Build(ctx)
76+
if err != nil {
77+
return fmt.Errorf("build process: %w", err)
78+
}
79+
80+
return nil
81+
}

pkg/commands/internal/builder.go

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
package internal
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"os"
8+
"os/exec"
9+
"path/filepath"
10+
"runtime"
11+
"strings"
12+
"time"
13+
14+
"github.com/golangci/golangci-lint/pkg/logutils"
15+
)
16+
17+
// Builder runs all the required commands to build a binary.
18+
type Builder struct {
19+
cfg *Configuration
20+
21+
log logutils.Log
22+
23+
root string
24+
repo string
25+
}
26+
27+
// NewBuilder creates a new Builder.
28+
func NewBuilder(logger logutils.Log, cfg *Configuration, root string) *Builder {
29+
return &Builder{
30+
cfg: cfg,
31+
log: logger,
32+
root: root,
33+
repo: filepath.Join(root, "golangci-lint"),
34+
}
35+
}
36+
37+
// Build builds the custom binary.
38+
func (b Builder) Build(ctx context.Context) error {
39+
b.log.Infof("Cloning golangci-lint repository.")
40+
41+
err := b.clone(ctx)
42+
if err != nil {
43+
return fmt.Errorf("clone golangci-lint: %w", err)
44+
}
45+
46+
b.log.Infof("Adding plugin imports.")
47+
48+
err = b.updatePluginsFile()
49+
if err != nil {
50+
return fmt.Errorf("update plugin file: %w", err)
51+
}
52+
53+
b.log.Infof("Adding replace directives.")
54+
55+
err = b.addReplaceDirectives(ctx)
56+
if err != nil {
57+
return fmt.Errorf("add replace directives: %w", err)
58+
}
59+
60+
b.log.Infof("Running go mod tidy.")
61+
62+
err = b.goModTidy(ctx)
63+
if err != nil {
64+
return fmt.Errorf("go mod tidy: %w", err)
65+
}
66+
67+
b.log.Infof("Building golangci-lint binary.")
68+
69+
binaryName := b.getBinaryName()
70+
71+
err = b.goBuild(ctx, binaryName)
72+
if err != nil {
73+
return fmt.Errorf("build golangci-lint binary: %w", err)
74+
}
75+
76+
b.log.Infof("Moving golangci-lint binary.")
77+
78+
err = b.copyBinary(binaryName)
79+
if err != nil {
80+
return fmt.Errorf("move golangci-lint binary: %w", err)
81+
}
82+
83+
return nil
84+
}
85+
86+
func (b Builder) clone(ctx context.Context) error {
87+
//nolint:gosec
88+
cmd := exec.CommandContext(ctx,
89+
"git", "clone", "--branch", b.cfg.Version,
90+
"--single-branch", "--depth", "1", "-c advice.detachedHead=false", "-q",
91+
"https://github.com/ldez/golangci-lint.git", // FIXME(ldez) use https://github.com/golangci/golangci-lint.git
92+
)
93+
cmd.Dir = b.root
94+
95+
output, err := cmd.CombinedOutput()
96+
if err != nil {
97+
b.log.Infof(string(output))
98+
99+
return fmt.Errorf("%s: %w", strings.Join(cmd.Args, " "), err)
100+
}
101+
102+
return nil
103+
}
104+
105+
func (b Builder) addReplaceDirectives(ctx context.Context) error {
106+
for _, plugin := range b.cfg.Plugins {
107+
if plugin.Path == "" {
108+
continue
109+
}
110+
111+
replace := fmt.Sprintf("%s=%s", plugin.Module, plugin.Path)
112+
113+
cmd := exec.CommandContext(ctx, "go", "mod", "edit", "-replace", replace)
114+
cmd.Dir = b.repo
115+
116+
b.log.Infof("run: %s", strings.Join(cmd.Args, " "))
117+
118+
output, err := cmd.CombinedOutput()
119+
if err != nil {
120+
b.log.Warnf(string(output))
121+
122+
return fmt.Errorf("%s: %w", strings.Join(cmd.Args, " "), err)
123+
}
124+
}
125+
126+
return nil
127+
}
128+
129+
func (b Builder) goModTidy(ctx context.Context) error {
130+
cmd := exec.CommandContext(ctx, "go", "mod", "tidy")
131+
cmd.Dir = b.repo
132+
133+
output, err := cmd.CombinedOutput()
134+
if err != nil {
135+
b.log.Warnf(string(output))
136+
137+
return fmt.Errorf("%s: %w", strings.Join(cmd.Args, " "), err)
138+
}
139+
140+
return nil
141+
}
142+
143+
func (b Builder) goBuild(ctx context.Context, binaryName string) error {
144+
//nolint:gosec
145+
cmd := exec.CommandContext(ctx, "go", "build",
146+
"-ldflags",
147+
fmt.Sprintf(
148+
"-s -w -X 'main.version=%s-mygcl' -X 'main.date=%s'",
149+
b.cfg.Version, time.Now().UTC().String(),
150+
),
151+
"-o", binaryName,
152+
"./cmd/golangci-lint",
153+
)
154+
cmd.Dir = b.repo
155+
156+
output, err := cmd.CombinedOutput()
157+
if err != nil {
158+
b.log.Warnf(string(output))
159+
160+
return fmt.Errorf("%s: %w", strings.Join(cmd.Args, " "), err)
161+
}
162+
163+
return nil
164+
}
165+
166+
func (b Builder) copyBinary(binaryName string) error {
167+
src := filepath.Join(b.repo, binaryName)
168+
169+
source, err := os.Open(filepath.Clean(src))
170+
if err != nil {
171+
return fmt.Errorf("open source file: %w", err)
172+
}
173+
174+
defer func() { _ = source.Close() }()
175+
176+
info, err := source.Stat()
177+
if err != nil {
178+
return fmt.Errorf("stat source file: %w", err)
179+
}
180+
181+
if b.cfg.Destination != "" {
182+
err = os.MkdirAll(b.cfg.Destination, os.ModePerm)
183+
if err != nil {
184+
return fmt.Errorf("create destination directory: %w", err)
185+
}
186+
}
187+
188+
dst, err := os.OpenFile(filepath.Join(b.cfg.Destination, binaryName), os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode())
189+
if err != nil {
190+
return fmt.Errorf("create destination file: %w", err)
191+
}
192+
193+
defer func() { _ = dst.Close() }()
194+
195+
_, err = io.Copy(dst, source)
196+
if err != nil {
197+
return fmt.Errorf("copy source to destination: %w", err)
198+
}
199+
200+
return nil
201+
}
202+
203+
func (b Builder) getBinaryName() string {
204+
name := b.cfg.Name
205+
if runtime.GOOS == "windows" {
206+
name += ".exe"
207+
}
208+
209+
return name
210+
}

0 commit comments

Comments
 (0)