mirror of
https://github.com/Luzifer/update-gotools.git
synced 2024-12-22 12:51:20 +00:00
Initial version
This commit is contained in:
commit
beca7dd6b1
3 changed files with 247 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
update-gotools
|
28
limiter.go
Normal file
28
limiter.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package main
|
||||
|
||||
import "sync"
|
||||
|
||||
type limiter struct {
|
||||
l chan struct{}
|
||||
w sync.WaitGroup
|
||||
}
|
||||
|
||||
func newLimiter(max int) *limiter {
|
||||
return &limiter{
|
||||
l: make(chan struct{}, max),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *limiter) Add() {
|
||||
l.l <- struct{}{}
|
||||
l.w.Add(1)
|
||||
}
|
||||
|
||||
func (l *limiter) Done() {
|
||||
<-l.l
|
||||
l.w.Done()
|
||||
}
|
||||
|
||||
func (l *limiter) Wait() {
|
||||
l.w.Wait()
|
||||
}
|
218
main.go
Normal file
218
main.go
Normal file
|
@ -0,0 +1,218 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/Luzifer/go_helpers/str"
|
||||
"github.com/Luzifer/rconfig"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type pkgCfg struct {
|
||||
Name string `yaml:"name"`
|
||||
Single bool `yaml:"single"`
|
||||
Version string `yaml:"version"`
|
||||
}
|
||||
|
||||
type configFile struct {
|
||||
Cwd string `yaml:"cwd"`
|
||||
GoPath string `yaml:"gopath"`
|
||||
Packages []pkgCfg `yaml:"packages"`
|
||||
PreCommands [][]string `yaml:"pre_commands"`
|
||||
PostCommands [][]string `yaml:"post_commands"`
|
||||
}
|
||||
|
||||
var (
|
||||
cfg = struct {
|
||||
Config string `flag:"config,c" default:"~/.config/gotools.yml" description:"Configuration for update-gotools utility"`
|
||||
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"`
|
||||
VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"`
|
||||
}{}
|
||||
|
||||
version = "dev"
|
||||
|
||||
cfgFile *configFile
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
if err = rconfig.Parse(&cfg); err != nil {
|
||||
log.WithError(err).Fatal("Unable to parse commandline options")
|
||||
}
|
||||
|
||||
if l, err := log.ParseLevel(cfg.LogLevel); err != nil {
|
||||
log.WithError(err).Fatal("Unable to parse log level")
|
||||
} else {
|
||||
log.SetLevel(l)
|
||||
}
|
||||
|
||||
if cfg.VersionAndExit {
|
||||
fmt.Printf("update-gotools %s\n", version)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if cfg.Config, err = homedir.Expand(cfg.Config); err != nil {
|
||||
log.WithError(err).Fatal("Unable to expand config path")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
|
||||
cfgFile, err = defaultConfig()
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("Unable to create default config")
|
||||
}
|
||||
|
||||
cfgFileRaw, err := ioutil.ReadFile(cfg.Config)
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("Unable to read config")
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(cfgFileRaw, cfgFile); err != nil {
|
||||
log.WithError(err).Fatal("Unable to parse config file")
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"version": version,
|
||||
"num_cpu": runtime.NumCPU(),
|
||||
}).Debugf("update-gotools started")
|
||||
|
||||
runPreCommands()
|
||||
runPackageBuilds(runtime.NumCPU()-1, func(pkg pkgCfg) bool { return !pkg.Single })
|
||||
runPackageBuilds(1, func(pkg pkgCfg) bool { return pkg.Single })
|
||||
runPostCommands()
|
||||
|
||||
log.Info("Installation successful")
|
||||
}
|
||||
|
||||
func runPreCommands() {
|
||||
log.Info("Executing pre-commands")
|
||||
if err := executeCommands(log.WithFields(log.Fields{
|
||||
"step": "pre_commands",
|
||||
}), cfgFile.PreCommands); err != nil {
|
||||
log.WithError(err).Fatal("Pre-Command failed")
|
||||
}
|
||||
}
|
||||
|
||||
func runPackageBuilds(n int, filter func(pkgCfg) bool) {
|
||||
limit := newLimiter(n)
|
||||
for _, pkg := range cfgFile.Packages {
|
||||
if !filter(pkg) {
|
||||
continue
|
||||
}
|
||||
|
||||
limit.Add()
|
||||
go func(pkg pkgCfg) {
|
||||
logVer := pkg.Version
|
||||
if logVer == "" {
|
||||
logVer = "HEAD"
|
||||
}
|
||||
|
||||
pkgLog := log.WithFields(log.Fields{
|
||||
"pkg": pkg.Name,
|
||||
"ver": logVer,
|
||||
})
|
||||
|
||||
if err := executePackage(pkg, pkgLog); err != nil {
|
||||
pkgLog.WithError(err).Fatal("Failed to install package")
|
||||
}
|
||||
limit.Done()
|
||||
}(pkg)
|
||||
}
|
||||
limit.Wait()
|
||||
}
|
||||
|
||||
func runPostCommands() {
|
||||
log.Info("Executing post-commands")
|
||||
if err := executeCommands(log.WithFields(log.Fields{
|
||||
"step": "post_commands",
|
||||
}), cfgFile.PostCommands); err != nil {
|
||||
log.WithError(err).Fatal("Post-Command failed")
|
||||
}
|
||||
}
|
||||
|
||||
func defaultConfig() (*configFile, error) {
|
||||
h, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &configFile{
|
||||
Cwd: h,
|
||||
Packages: []pkgCfg{},
|
||||
PreCommands: [][]string{},
|
||||
PostCommands: [][]string{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func executeCommands(logEntry *log.Entry, commands [][]string) error {
|
||||
for i, cmdCfg := range commands {
|
||||
stderr := logEntry.WithFields(log.Fields{"cmd_id": i}).WriterLevel(log.ErrorLevel)
|
||||
stdout := logEntry.WithFields(log.Fields{"cmd_id": i}).WriterLevel(log.InfoLevel)
|
||||
defer stderr.Close()
|
||||
defer stdout.Close()
|
||||
|
||||
if err := executeCommand(cmdCfg, stdout, stderr, cfgFile.Cwd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func executeCommand(command []string, stdout, stderr io.Writer, cwd string) error {
|
||||
env := []string{
|
||||
strings.Join([]string{"GOPATH", cfgFile.GoPath}, "="),
|
||||
strings.Join([]string{"PATH", os.Getenv("PATH")}, "="),
|
||||
}
|
||||
|
||||
cmd := exec.Command(command[0], command[1:]...)
|
||||
cmd.Dir = cwd
|
||||
cmd.Env = env
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func executePackage(pkg pkgCfg, pkgLog *log.Entry) error {
|
||||
pkgLog.Info("Started package installation")
|
||||
|
||||
stderr := pkgLog.WriterLevel(log.ErrorLevel)
|
||||
stdout := pkgLog.WriterLevel(log.InfoLevel)
|
||||
defer stderr.Close()
|
||||
defer stdout.Close()
|
||||
|
||||
pkgLog.Debug("Fetching package using `go get`")
|
||||
if err := executeCommand([]string{"go", "get", "-d", pkg.Name}, stdout, stderr, cfgFile.Cwd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !str.StringInSlice(pkg.Version, []string{"", "master"}) {
|
||||
pkgLog.Debug("Resetting to specified version")
|
||||
pkgPath := path.Join(os.Getenv("GOPATH"), "src", pkg.Name)
|
||||
|
||||
// Fetch required references
|
||||
if err := executeCommand([]string{"git", "fetch", "-q", "--tags", "origin", pkg.Version}, stdout, stderr, pkgPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Do the real reset
|
||||
if err := executeCommand([]string{"git", "reset", "--hard", pkg.Version}, stdout, stderr, pkgPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
pkgLog.Debug("Install package using `go install`")
|
||||
return executeCommand([]string{"go", "install", pkg.Name}, stdout, stderr, cfgFile.Cwd)
|
||||
}
|
Loading…
Reference in a new issue