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:
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
|
||||
}
|
||||
Reference in New Issue
Block a user