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()) }