220 lines
4.6 KiB
Go
220 lines
4.6 KiB
Go
package internal
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
|
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
|
)
|
|
|
|
// Builder runs all the required commands to build a binary.
|
|
type Builder struct {
|
|
cfg *Configuration
|
|
|
|
log logutils.Log
|
|
|
|
root string
|
|
repo string
|
|
}
|
|
|
|
// NewBuilder creates a new Builder.
|
|
func NewBuilder(logger logutils.Log, cfg *Configuration, root string) *Builder {
|
|
return &Builder{
|
|
cfg: cfg,
|
|
log: logger,
|
|
root: root,
|
|
repo: filepath.Join(root, "golangci-lint"),
|
|
}
|
|
}
|
|
|
|
// Build builds the custom binary.
|
|
func (b Builder) Build(ctx context.Context) error {
|
|
b.log.Infof("Cloning golangci-lint repository")
|
|
|
|
err := b.clone(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("clone golangci-lint: %w", err)
|
|
}
|
|
|
|
b.log.Infof("Adding plugin imports")
|
|
|
|
err = b.updatePluginsFile()
|
|
if err != nil {
|
|
return fmt.Errorf("update plugin file: %w", err)
|
|
}
|
|
|
|
b.log.Infof("Adding replace directives")
|
|
|
|
err = b.addReplaceDirectives(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("add replace directives: %w", err)
|
|
}
|
|
|
|
b.log.Infof("Running go mod tidy")
|
|
|
|
err = b.goModTidy(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("go mod tidy: %w", err)
|
|
}
|
|
|
|
b.log.Infof("Building golangci-lint binary")
|
|
|
|
binaryName := b.getBinaryName()
|
|
|
|
err = b.goBuild(ctx, binaryName)
|
|
if err != nil {
|
|
return fmt.Errorf("build golangci-lint binary: %w", err)
|
|
}
|
|
|
|
b.log.Infof("Moving golangci-lint binary")
|
|
|
|
err = b.copyBinary(binaryName)
|
|
if err != nil {
|
|
return fmt.Errorf("move golangci-lint binary: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b Builder) clone(ctx context.Context) error {
|
|
//nolint:gosec // the variable is sanitized.
|
|
cmd := exec.CommandContext(ctx,
|
|
"git", "clone", "--branch", sanitizeVersion(b.cfg.Version),
|
|
"--single-branch", "--depth", "1", "-c advice.detachedHead=false", "-q",
|
|
"https://github.com/golangci/golangci-lint.git",
|
|
)
|
|
cmd.Dir = b.root
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
b.log.Infof(string(output))
|
|
|
|
return fmt.Errorf("%s: %w", strings.Join(cmd.Args, " "), err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b Builder) addReplaceDirectives(ctx context.Context) error {
|
|
for _, plugin := range b.cfg.Plugins {
|
|
if plugin.Path == "" {
|
|
continue
|
|
}
|
|
|
|
replace := fmt.Sprintf("%s=%s", plugin.Module, plugin.Path)
|
|
|
|
cmd := exec.CommandContext(ctx, "go", "mod", "edit", "-replace", replace)
|
|
cmd.Dir = b.repo
|
|
|
|
b.log.Infof("run: %s", strings.Join(cmd.Args, " "))
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
b.log.Warnf(string(output))
|
|
|
|
return fmt.Errorf("%s: %w", strings.Join(cmd.Args, " "), err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b Builder) goModTidy(ctx context.Context) error {
|
|
cmd := exec.CommandContext(ctx, "go", "mod", "tidy")
|
|
cmd.Dir = b.repo
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
b.log.Warnf(string(output))
|
|
|
|
return fmt.Errorf("%s: %w", strings.Join(cmd.Args, " "), err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b Builder) goBuild(ctx context.Context, binaryName string) error {
|
|
//nolint:gosec // the variable is sanitized.
|
|
cmd := exec.CommandContext(ctx, "go", "build",
|
|
"-ldflags",
|
|
fmt.Sprintf(
|
|
"-s -w -X 'main.version=%s-custom-gcl' -X 'main.date=%s'",
|
|
sanitizeVersion(b.cfg.Version), time.Now().UTC().String(),
|
|
),
|
|
"-o", binaryName,
|
|
"./cmd/golangci-lint",
|
|
)
|
|
cmd.Dir = b.repo
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
b.log.Warnf(string(output))
|
|
|
|
return fmt.Errorf("%s: %w", strings.Join(cmd.Args, " "), err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b Builder) copyBinary(binaryName string) error {
|
|
src := filepath.Join(b.repo, binaryName)
|
|
|
|
source, err := os.Open(filepath.Clean(src))
|
|
if err != nil {
|
|
return fmt.Errorf("open source file: %w", err)
|
|
}
|
|
|
|
defer func() { _ = source.Close() }()
|
|
|
|
info, err := source.Stat()
|
|
if err != nil {
|
|
return fmt.Errorf("stat source file: %w", err)
|
|
}
|
|
|
|
if b.cfg.Destination != "" {
|
|
err = os.MkdirAll(b.cfg.Destination, os.ModePerm)
|
|
if err != nil {
|
|
return fmt.Errorf("create destination directory: %w", err)
|
|
}
|
|
}
|
|
|
|
dst, err := os.OpenFile(filepath.Join(b.cfg.Destination, binaryName), os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode())
|
|
if err != nil {
|
|
return fmt.Errorf("create destination file: %w", err)
|
|
}
|
|
|
|
defer func() { _ = dst.Close() }()
|
|
|
|
_, err = io.Copy(dst, source)
|
|
if err != nil {
|
|
return fmt.Errorf("copy source to destination: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b Builder) getBinaryName() string {
|
|
name := b.cfg.Name
|
|
if runtime.GOOS == "windows" {
|
|
name += ".exe"
|
|
}
|
|
|
|
return name
|
|
}
|
|
|
|
func sanitizeVersion(v string) string {
|
|
fn := func(c rune) bool {
|
|
return !(unicode.IsLetter(c) || unicode.IsNumber(c) || c == '.' || c == '/')
|
|
}
|
|
|
|
return strings.Join(strings.FieldsFunc(v, fn), "")
|
|
}
|