- 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
123 lines
2.7 KiB
Go
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())
|
|
}
|