diff --git a/argv.go b/argv.go index 788f8ba..f5da074 100644 --- a/argv.go +++ b/argv.go @@ -7,7 +7,8 @@ package main var argv args type args struct { - Repo string `arg:"positional" help:"gopath (otherwise uses "pwd")"` + Recursive bool `arg:"--recursive" default:"false" help:"clean every repo found in go/src or go.work"` + Auto bool `arg:"--auto" help:"don't approve via STDIN"` } func (args) Version() string { diff --git a/main.go b/main.go index 8e5d501..8b3c85e 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,14 @@ package main import ( + "errors" + "fmt" "os" + "path/filepath" + "strings" "go.wit.com/dev/alexflint/arg" + "go.wit.com/lib/gui/shell" "go.wit.com/lib/protobuf/forgepb" "go.wit.com/lib/protobuf/gitpb" "go.wit.com/log" @@ -21,46 +26,153 @@ func main() { log.Info("go-clean version", VERSION, "built on", BUILDTIME) pp = arg.MustParse(&argv) - // for very new users or users unfamilar with the command line, this may help them - if argv.Repo == "help" || argv.Repo == "?" { - pp.WriteHelp(os.Stdout) - os.Exit(0) - } - if argv.Repo == "version" { - log.Info(argv.Version()) - os.Exit(0) - } - // load the ~/.config/forge/ config // this lets you configure repos you have read/write access too forge = forgepb.Init() + // rescan just in case (?) todo: decide what forge should default too + forge.ScanGoSrc() - check = forge.Repos.FindByGoPath(argv.Repo) + // figure out what directory we are running in + check = findPwdRepo() if check == nil { - log.Info("need to go-clone", argv.Repo) - return + log.Info("this directory isn't in a golang project (not in ~/go/src nor a go.work file)") + os.Exit(-1) } - log.Info("already have", argv.Repo) + log.Info("starting go-clean for", check.GoPath) + log.Info("go src dir is set to", forge.GetGoSrc()) - log.Info("go src dir", forge.GetGoSrc()) - // re-create go.sum and go.mod - check.RedoGoMod() - deps := check.GoDeps.SortByGoPath() - for deps.Scan() { - depRepo := deps.Next() - log.Info("check has dep:", depRepo.GoPath) + if argv.Recursive { + if forge.IsGoWork() { + var warning []string + warning = append(warning, "go-clean --recursive may not work unless you are in ~/go/src") + warning = append(warning, "you can continue anyway, but it hasn't been tested as much.") + simpleStdin(true, warning) + } + var warning []string + warning = append(warning, "go-clean will recreate go.mod and go.sum") + warning = append(warning, "because you have selected --recursive") + warning = append(warning, "this will redo _every_ repo. This is probably fine.") + warning = append(warning, fmt.Sprintf("You have %d total repositories in %s", forge.Repos.Len(), forge.GetGoSrc())) + warning = append(warning, "") + warning = append(warning, "However, this will also do:") + warning = append(warning, "") + warning = append(warning, "rm -rf ~/go/pkg/") + warning = append(warning, "rm -rf ~/.config/go-build/") + warning = append(warning, "") + warning = append(warning, "Which is also probably fine, but will clear all your build cache and go mod cache") + warning = append(warning, "") + simpleStdin(false, warning) + purgeGoCaches() + } else { + simpleStdin(true, []string{"go-clean will recreate go.mod and go.sum"}) } - okExit("skipping build of " + argv.Repo) + + // re-create go.sum and go.mod + if _, err := check.RedoGoMod(); err != nil { + badExit(err) + } + + /* + // re-process go deps + deps := check.GoDeps.SortByGoPath() + for deps.Scan() { + depRepo := deps.Next() + log.Info("check has dep:", depRepo.GoPath) + } + */ + // check go.sum file + if forge.FinalGoDepsCheckOk(check) { + log.Info("forge.FinalGoDepsCheck() worked :", check.GoPath) + okExit(check.GoPath + " go.sum seems clean") + } + log.Info("forge.FinalGoDepsCheck() failed. boo. :", check.GoPath) + badExit(errors.New(check.GoPath + " go.sum is not perfect")) +} + +func findPwdRepo() *gitpb.Repo { + var check *gitpb.Repo + // attempt to use the working directory + // this is probably what happens most of the time + pwd, _ := os.Getwd() + if strings.HasPrefix(pwd, forge.GetGoSrc()) { + gopath := strings.TrimPrefix(pwd, forge.GetGoSrc()) + gopath = strings.Trim(gopath, "/") + log.Info("findRepo() trying gopath", gopath) + check = forge.Repos.FindByGoPath(gopath) + if check != nil { + log.Info("findRepo() worked", check.GoPath) + return check + } + } + return nil } func okExit(thing string) { log.Info(thing, "ok") - log.Info("Finished clone on", check.GetGoPath(), "ok") + log.Info("Finished go-clean on", check.GetGoPath(), "ok") os.Exit(0) } func badExit(err error) { - log.Info("Total repositories:", forge.Repos.Len()) log.Info("Finished go-clean with error", err, forge.GetGoSrc()) os.Exit(-1) } + +func purgeGoCaches() { + homedir, err := os.UserHomeDir() + if err != nil { + badExit(err) + } + pkgdir := filepath.Join(homedir, "go/pkg") + var cmd []string + cmd = []string{"chmod", "700", "-R", pkgdir} + runStrict("", cmd) + cmd = []string{"rm", "-rf", pkgdir} + runStrict("", cmd) + builddir := filepath.Join(homedir, ".cache/go-build") + cmd = []string{"rm", "-rf", builddir} + runStrict("", cmd) + + // this can't have really happened + // echo this still failed in: + // echo "Linux hpdev2.grid.wit.com 6.9.8-rt-amd64 #1 SMP PREEMPT_RT Debian 6.9.8-1 (2024-07-07) x86_64 GNU/Linux" + // echo and I had to drop the caches after building go install binaries quickly and running them + // echo "as an os.Exec() between binaries" + // echo sysctl -w vm.drop_caches=3 +} + +func runStrict(wd string, cmd []string) { + var err error + if wd != "" { + if err = os.Chdir(wd); err != nil { + log.Info("cd", "wd", "failed", err) + badExit(err) + } + } + log.Info(wd, "running:", wd, cmd) + // result := shell.Run(cmd) + result := shell.RunRealtime(cmd) + if result.Error != nil { + log.Info("cmd failed", wd, cmd, err) + for i, line := range result.Stdout { + log.Info("STDOUT:", i, line) + } + for i, line := range result.Stderr { + log.Info("STDERR:", i, line) + } + badExit(err) + } + if result.Exit != 0 { + log.Info("cmd failed", wd, cmd, err) + for i, line := range result.Stdout { + log.Info("STDOUT:", i, line) + } + for i, line := range result.Stderr { + log.Info("STDERR:", i, line) + } + badExit(errors.New(fmt.Sprintf("cmd failed with %d", result.Exit))) + } + for i, line := range result.Stdout { + log.Info(i, line) + } +} diff --git a/stdin.go b/stdin.go new file mode 100644 index 0000000..9093246 --- /dev/null +++ b/stdin.go @@ -0,0 +1,55 @@ +package main + +import ( + "bufio" + "errors" + "fmt" + "os" + "strings" + + "go.wit.com/log" +) + +func showOptions(b bool, s []string) { + fmt.Println("") + for _, line := range s { + fmt.Println(line) + } + fmt.Println("") + if b { + fmt.Println("Enter (Y/n)") + } else { + fmt.Println("Enter (y/N)") + } +} + +// if b == true, default is to continue with 'Y' +func simpleStdin(b bool, s []string) { + if argv.Auto { + return + } + err := errors.New("user cancelled via stdin") + showOptions(b, s) + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + s := scanner.Text() + s = strings.TrimSpace(s) + // s = strings.Lower(s) + switch s { + case "y": + log.Info("got y") + return + case "n": + log.Info("got n") + badExit(err) + case "": + if b { + return + } else { + badExit(err) + } + default: + badExit(err) + } + } +}