Initial commit: gosetup tool for installing Go dev binaries
Installs common Go development tools at pinned versions including vim-go binaries, linters, formatters, and debugging tools.
This commit is contained in:
commit
7188d87c39
45
README.md
Normal file
45
README.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# gosetup
|
||||||
|
|
||||||
|
Installs common Go development tools at pinned versions.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install sneak.berlin/go/gosetup@COMMIT_HASH
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install all tools at pinned versions
|
||||||
|
gosetup
|
||||||
|
|
||||||
|
# List tools and versions without installing
|
||||||
|
gosetup -l
|
||||||
|
|
||||||
|
# Dry run (show commands without executing)
|
||||||
|
gosetup -n
|
||||||
|
|
||||||
|
# Verbose output
|
||||||
|
gosetup -v
|
||||||
|
|
||||||
|
# Control parallelism (default: 4)
|
||||||
|
gosetup -j 8
|
||||||
|
```
|
||||||
|
|
||||||
|
## Updating Pinned Versions
|
||||||
|
|
||||||
|
To update `tools.json` with the latest versions:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run ./cmd/update -o tools.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Included Tools
|
||||||
|
|
||||||
|
- **Language Server**: gopls
|
||||||
|
- **Formatters**: gofumpt, goimports, golines
|
||||||
|
- **Linters**: golangci-lint, staticcheck, errcheck, revive
|
||||||
|
- **vim-go tools**: godef, guru, gorename, gotests, gomodifytags, impl, fillstruct, fillswitch, motion, iferr, keyify, asmfmt, gotags
|
||||||
|
- **Debugger**: dlv (delve)
|
||||||
|
- **And more**: gocyclo, ineffassign, misspell, unconvert, gopkgs, go-outline, go-symbols
|
||||||
169
cmd/update/main.go
Normal file
169
cmd/update/main.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tool struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Package string `json:"package"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Date string `json:"date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToolsFile struct {
|
||||||
|
Tools []Tool `json:"tools"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModuleInfo struct {
|
||||||
|
Version string `json:"Version"`
|
||||||
|
Time string `json:"Time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
outputPath := flag.String("o", "tools.json", "output file path")
|
||||||
|
parallel := flag.Int("j", 8, "number of parallel lookups")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// Read existing tools.json to get the package list
|
||||||
|
data, err := os.ReadFile(*outputPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error reading %s: %v\n", *outputPath, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tf ToolsFile
|
||||||
|
if err := json.Unmarshal(data, &tf); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error parsing %s: %v\n", *outputPath, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Updating %d tools...\n\n", len(tf.Tools))
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
sem := make(chan struct{}, *parallel)
|
||||||
|
results := make(chan result, len(tf.Tools))
|
||||||
|
|
||||||
|
for i, t := range tf.Tools {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(idx int, tool Tool) {
|
||||||
|
defer wg.Done()
|
||||||
|
sem <- struct{}{}
|
||||||
|
defer func() { <-sem }()
|
||||||
|
|
||||||
|
updated, err := fetchLatestVersion(tool)
|
||||||
|
results <- result{idx: idx, tool: tool, updated: updated, err: err}
|
||||||
|
}(i, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(results)
|
||||||
|
}()
|
||||||
|
|
||||||
|
updatedTools := make([]Tool, len(tf.Tools))
|
||||||
|
var succeeded, failed, changed int
|
||||||
|
|
||||||
|
for r := range results {
|
||||||
|
if r.err != nil {
|
||||||
|
fmt.Printf("✗ %s: %v\n", r.tool.Name, r.err)
|
||||||
|
updatedTools[r.idx] = r.tool // keep old version
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
updatedTools[r.idx] = r.updated
|
||||||
|
if r.tool.Version != r.updated.Version {
|
||||||
|
fmt.Printf("↑ %s: %s -> %s\n", r.tool.Name, r.tool.Version, r.updated.Version)
|
||||||
|
changed++
|
||||||
|
} else {
|
||||||
|
fmt.Printf("✓ %s: %s (unchanged)\n", r.tool.Name, r.tool.Version)
|
||||||
|
}
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tf.Tools = updatedTools
|
||||||
|
|
||||||
|
// Write updated tools.json
|
||||||
|
output, err := json.MarshalIndent(tf, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error marshaling JSON: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
output = append(output, '\n')
|
||||||
|
|
||||||
|
absPath, _ := filepath.Abs(*outputPath)
|
||||||
|
if err := os.WriteFile(*outputPath, output, 0644); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error writing %s: %v\n", *outputPath, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\nDone: %d succeeded, %d failed, %d updated\n", succeeded, failed, changed)
|
||||||
|
fmt.Printf("Written to %s\n", absPath)
|
||||||
|
|
||||||
|
if failed > 0 {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getModulePath extracts the module path from a package path
|
||||||
|
// e.g., "golang.org/x/tools/cmd/goimports" -> "golang.org/x/tools"
|
||||||
|
func getModulePath(pkg string) string {
|
||||||
|
// Handle special cases where cmd is in a subpath
|
||||||
|
if strings.Contains(pkg, "/cmd/") {
|
||||||
|
parts := strings.Split(pkg, "/cmd/")
|
||||||
|
return parts[0]
|
||||||
|
}
|
||||||
|
// For packages like "github.com/cweill/gotests/gotests"
|
||||||
|
// we need the parent module
|
||||||
|
if strings.Contains(pkg, "/gojson") && strings.Contains(pkg, "ChimeraCoder") {
|
||||||
|
return "github.com/ChimeraCoder/gojson"
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(pkg, "/gotests") {
|
||||||
|
return strings.TrimSuffix(pkg, "/gotests")
|
||||||
|
}
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchLatestVersion(tool Tool) (Tool, error) {
|
||||||
|
modPath := getModulePath(tool.Package)
|
||||||
|
cmd := exec.Command("go", "list", "-m", "-json", modPath+"@latest")
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return Tool{}, fmt.Errorf("go list failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var info ModuleInfo
|
||||||
|
if err := json.Unmarshal(output, &info); err != nil {
|
||||||
|
return Tool{}, fmt.Errorf("parse error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse date
|
||||||
|
date := ""
|
||||||
|
if info.Time != "" {
|
||||||
|
if t, err := time.Parse(time.RFC3339, info.Time); err == nil {
|
||||||
|
date = t.Format("2006-01-02")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Tool{
|
||||||
|
Name: tool.Name,
|
||||||
|
Package: tool.Package,
|
||||||
|
Version: info.Version,
|
||||||
|
Date: date,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type result struct {
|
||||||
|
idx int
|
||||||
|
tool Tool
|
||||||
|
updated Tool
|
||||||
|
err error
|
||||||
|
}
|
||||||
108
main.go
Normal file
108
main.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed tools.json
|
||||||
|
var toolsJSON []byte
|
||||||
|
|
||||||
|
type Tool struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Package string `json:"package"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Date string `json:"date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToolsFile struct {
|
||||||
|
Tools []Tool `json:"tools"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
parallel := flag.Int("j", 4, "number of parallel installs")
|
||||||
|
dryRun := flag.Bool("n", false, "dry run (print commands without executing)")
|
||||||
|
verbose := flag.Bool("v", false, "verbose output")
|
||||||
|
list := flag.Bool("l", false, "list tools and versions without installing")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
var tf ToolsFile
|
||||||
|
if err := json.Unmarshal(toolsJSON, &tf); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error parsing embedded tools.json: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *list {
|
||||||
|
for _, t := range tf.Tools {
|
||||||
|
fmt.Printf("%-20s %s (%s)\n", t.Name, t.Version, t.Date)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Installing %d Go development tools...\n\n", len(tf.Tools))
|
||||||
|
|
||||||
|
if *dryRun {
|
||||||
|
for _, t := range tf.Tools {
|
||||||
|
fmt.Printf("go install %s@%s\n", t.Package, t.Version)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
sem := make(chan struct{}, *parallel)
|
||||||
|
results := make(chan result, len(tf.Tools))
|
||||||
|
|
||||||
|
for _, t := range tf.Tools {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(tool Tool) {
|
||||||
|
defer wg.Done()
|
||||||
|
sem <- struct{}{}
|
||||||
|
defer func() { <-sem }()
|
||||||
|
|
||||||
|
if *verbose {
|
||||||
|
fmt.Printf("Installing %s@%s...\n", tool.Name, tool.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg := fmt.Sprintf("%s@%s", tool.Package, tool.Version)
|
||||||
|
cmd := exec.Command("go", "install", pkg)
|
||||||
|
cmd.Env = os.Environ()
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
results <- result{tool: tool, err: err, output: string(output)}
|
||||||
|
}(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(results)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var succeeded, failed int
|
||||||
|
for r := range results {
|
||||||
|
if r.err != nil {
|
||||||
|
fmt.Printf("✗ %s@%s: %v\n", r.tool.Name, r.tool.Version, r.err)
|
||||||
|
if *verbose && r.output != "" {
|
||||||
|
fmt.Printf(" %s\n", r.output)
|
||||||
|
}
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
fmt.Printf("✓ %s@%s\n", r.tool.Name, r.tool.Version)
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\nDone: %d succeeded, %d failed\n", succeeded, failed)
|
||||||
|
if failed > 0 {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type result struct {
|
||||||
|
tool Tool
|
||||||
|
err error
|
||||||
|
output string
|
||||||
|
}
|
||||||
196
tools.json
Normal file
196
tools.json
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
{
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "gopls",
|
||||||
|
"package": "golang.org/x/tools/gopls",
|
||||||
|
"version": "v0.21.0",
|
||||||
|
"date": "2025-12-05"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gofumpt",
|
||||||
|
"package": "mvdan.cc/gofumpt",
|
||||||
|
"version": "v0.9.2",
|
||||||
|
"date": "2025-10-21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "goimports",
|
||||||
|
"package": "golang.org/x/tools/cmd/goimports",
|
||||||
|
"version": "v0.40.0",
|
||||||
|
"date": "2025-12-08"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "golangci-lint",
|
||||||
|
"package": "github.com/golangci/golangci-lint/cmd/golangci-lint",
|
||||||
|
"version": "v1.64.8",
|
||||||
|
"date": "2025-03-17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "staticcheck",
|
||||||
|
"package": "honnef.co/go/tools/cmd/staticcheck",
|
||||||
|
"version": "v0.6.1",
|
||||||
|
"date": "2025-03-05"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "errcheck",
|
||||||
|
"package": "github.com/kisielk/errcheck",
|
||||||
|
"version": "v1.9.0",
|
||||||
|
"date": "2025-02-19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "revive",
|
||||||
|
"package": "github.com/mgechev/revive",
|
||||||
|
"version": "v1.13.0",
|
||||||
|
"date": "2025-11-13"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "godef",
|
||||||
|
"package": "github.com/rogpeppe/godef",
|
||||||
|
"version": "v1.1.2",
|
||||||
|
"date": "2020-03-03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "guru",
|
||||||
|
"package": "golang.org/x/tools/cmd/guru",
|
||||||
|
"version": "v0.40.0",
|
||||||
|
"date": "2025-12-08"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gorename",
|
||||||
|
"package": "golang.org/x/tools/cmd/gorename",
|
||||||
|
"version": "v0.40.0",
|
||||||
|
"date": "2025-12-08"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gotests",
|
||||||
|
"package": "github.com/cweill/gotests/gotests",
|
||||||
|
"version": "v1.9.0",
|
||||||
|
"date": "2025-10-23"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gomodifytags",
|
||||||
|
"package": "github.com/fatih/gomodifytags",
|
||||||
|
"version": "v1.17.0",
|
||||||
|
"date": "2024-07-15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "impl",
|
||||||
|
"package": "github.com/josharian/impl",
|
||||||
|
"version": "v1.5.0",
|
||||||
|
"date": "2025-12-09"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fillstruct",
|
||||||
|
"package": "github.com/davidrjenni/reftools/cmd/fillstruct",
|
||||||
|
"version": "v0.0.0-20250907133731-34b10582faa4",
|
||||||
|
"date": "2025-09-07"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fillswitch",
|
||||||
|
"package": "github.com/davidrjenni/reftools/cmd/fillswitch",
|
||||||
|
"version": "v0.0.0-20250907133731-34b10582faa4",
|
||||||
|
"date": "2025-09-07"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fixplurals",
|
||||||
|
"package": "github.com/davidrjenni/reftools/cmd/fixplurals",
|
||||||
|
"version": "v0.0.0-20250907133731-34b10582faa4",
|
||||||
|
"date": "2025-09-07"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dlv",
|
||||||
|
"package": "github.com/go-delve/delve/cmd/dlv",
|
||||||
|
"version": "v1.25.2",
|
||||||
|
"date": "2025-08-27"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gotags",
|
||||||
|
"package": "github.com/jstemmer/gotags",
|
||||||
|
"version": "v1.4.1",
|
||||||
|
"date": "2017-04-03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gogetdoc",
|
||||||
|
"package": "github.com/zmb3/gogetdoc",
|
||||||
|
"version": "v0.0.0-20190228002656-b37376c5da6a",
|
||||||
|
"date": "2019-02-28"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "iferr",
|
||||||
|
"package": "github.com/koron/iferr",
|
||||||
|
"version": "v0.0.0-20240122035601-9c3e2fbe4bd1",
|
||||||
|
"date": "2024-01-22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyify",
|
||||||
|
"package": "honnef.co/go/tools/cmd/keyify",
|
||||||
|
"version": "v0.6.1",
|
||||||
|
"date": "2025-03-05"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "asmfmt",
|
||||||
|
"package": "github.com/klauspost/asmfmt/cmd/asmfmt",
|
||||||
|
"version": "v1.3.2",
|
||||||
|
"date": "2022-03-30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "motion",
|
||||||
|
"package": "github.com/fatih/motion",
|
||||||
|
"version": "v1.2.0",
|
||||||
|
"date": "2023-02-16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gojson",
|
||||||
|
"package": "github.com/ChimeraCoder/gojson/gojson",
|
||||||
|
"version": "v1.1.0",
|
||||||
|
"date": "2018-08-18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "golines",
|
||||||
|
"package": "github.com/segmentio/golines",
|
||||||
|
"version": "v0.13.0",
|
||||||
|
"date": "2025-08-21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gocyclo",
|
||||||
|
"package": "github.com/fzipp/gocyclo/cmd/gocyclo",
|
||||||
|
"version": "v0.6.0",
|
||||||
|
"date": "2022-06-15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ineffassign",
|
||||||
|
"package": "github.com/gordonklaus/ineffassign",
|
||||||
|
"version": "v0.2.0",
|
||||||
|
"date": "2025-08-24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "misspell",
|
||||||
|
"package": "github.com/client9/misspell/cmd/misspell",
|
||||||
|
"version": "v0.3.4",
|
||||||
|
"date": "2018-03-09"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "unconvert",
|
||||||
|
"package": "github.com/mdempsky/unconvert",
|
||||||
|
"version": "v0.0.0-20250216222326-4a038b3d31f5",
|
||||||
|
"date": "2025-02-16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gopkgs",
|
||||||
|
"package": "github.com/uudashr/gopkgs/v2/cmd/gopkgs",
|
||||||
|
"version": "v2.1.2",
|
||||||
|
"date": "2020-02-14"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "go-outline",
|
||||||
|
"package": "github.com/ramya-rao-a/go-outline",
|
||||||
|
"version": "v0.0.0-20210608161538-9736a4bde949",
|
||||||
|
"date": "2021-06-08"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "go-symbols",
|
||||||
|
"package": "github.com/acroca/go-symbols",
|
||||||
|
"version": "v0.1.1",
|
||||||
|
"date": "2019-01-14"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user