gosetup/main.go
sneak 998dc1d37b Add version tags alongside commit hashes in output
- tools.json now has both "version" (tag) and "hash" (commit) fields
- Output format: toolname@version [hash] (in duration)
- Update tool fetches and stores both version and hash
- Fix .gitignore to not exclude cmd/update directory
2025-12-18 05:58:39 -08:00

123 lines
2.7 KiB
Go

package main
import (
_ "embed"
"encoding/json"
"flag"
"fmt"
"os"
"os/exec"
"sync"
"time"
)
//go:embed tools.json
var toolsJSON []byte
type Tool struct {
Name string `json:"name"`
Package string `json:"package"`
Version string `json:"version"`
Hash string `json:"hash"`
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] (%s)\n", t.Name, t.Version, t.Hash[:12], 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.Hash)
}
return
}
var wg sync.WaitGroup
sem := make(chan struct{}, *parallel)
results := make(chan result, len(tf.Tools))
startTime := time.Now()
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.Hash)
}
toolStart := time.Now()
pkg := fmt.Sprintf("%s@%s", tool.Package, tool.Hash)
cmd := exec.Command("go", "install", pkg)
cmd.Env = os.Environ()
output, err := cmd.CombinedOutput()
results <- result{tool: tool, err: err, output: string(output), duration: time.Since(toolStart)}
}(t)
}
go func() {
wg.Wait()
close(results)
}()
var succeeded, failed int
for r := range results {
if r.err != nil {
fmt.Printf("✗ %s@%s [%s]: %v (in %s)\n", r.tool.Name, r.tool.Version, r.tool.Hash[:12], r.err, formatDuration(r.duration))
if *verbose && r.output != "" {
fmt.Printf(" %s\n", r.output)
}
failed++
} else {
fmt.Printf("✓ %s@%s [%s] (in %s)\n", r.tool.Name, r.tool.Version, r.tool.Hash[:12], formatDuration(r.duration))
succeeded++
}
}
totalDuration := time.Since(startTime)
fmt.Printf("\nDone: %d succeeded, %d failed in %s\n", succeeded, failed, formatDuration(totalDuration))
if failed > 0 {
os.Exit(1)
}
}
type result struct {
tool Tool
err error
output string
duration time.Duration
}
func formatDuration(d time.Duration) string {
if d < time.Second {
return fmt.Sprintf("%dms", d.Milliseconds())
}
return fmt.Sprintf("%.1fs", d.Seconds())
}