X-Git-Url: http://www.git.cypherpunks.ru/?p=goredo.git;a=blobdiff_plain;f=main.go;h=9f1158182194446d20e8a2c0a103d9b4fa3d86f5;hp=ed82856be37f842e6c66f9a0607d191388cb40ac;hb=d8abe40c66df8d79a025524c0d230959cacf9465;hpb=07912df2f1935c7dd4e08f8e675ed989dbc97b31 diff --git a/main.go b/main.go index ed82856..9f11581 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,5 @@ /* -goredo -- redo implementation on pure Go +goredo -- djb's redo implementation on pure Go Copyright (C) 2020-2021 Sergey Matveev This program is free software: you can redistribute it and/or modify @@ -18,28 +18,53 @@ along with this program. If not, see . package main import ( + "bufio" + "bytes" "crypto/rand" + "errors" "flag" "fmt" "io" "io/ioutil" "log" "os" + "os/signal" "path" "path/filepath" + "runtime" + "sort" "strconv" - "strings" + "syscall" "go.cypherpunks.ru/recfile" + "golang.org/x/sys/unix" +) + +const ( + CmdNameGoredo = "goredo" + CmdNameRedo = "redo" + CmdNameRedoAffects = "redo-affects" + CmdNameRedoAlways = "redo-always" + CmdNameRedoCleanup = "redo-cleanup" + CmdNameRedoDot = "redo-dot" + CmdNameRedoIfchange = "redo-ifchange" + CmdNameRedoIfcreate = "redo-ifcreate" + CmdNameRedoLog = "redo-log" + CmdNameRedoOOD = "redo-ood" + CmdNameRedoSources = "redo-sources" + CmdNameRedoStamp = "redo-stamp" + CmdNameRedoTargets = "redo-targets" + CmdNameRedoWhichdo = "redo-whichdo" ) var ( Cwd string BuildUUID string + IsTopRedo bool // is it the top redo instance ) -func mustSetenv(key, value string) { - if err := os.Setenv(key, value); err != nil { +func mustSetenv(key string) { + if err := os.Setenv(key, "1"); err != nil { panic(err) } } @@ -56,33 +81,46 @@ func mustParseFd(v, name string) *os.File { return fd } +func CmdName() string { + return path.Base(os.Args[0]) +} + func main() { version := flag.Bool("version", false, "print version") warranty := flag.Bool("warranty", false, "print warranty information") - symlinks := flag.Bool("symlinks", false, "create necessary symlinks in current directory") - flag.Usage = usage + var symlinks *bool + cmdName := CmdName() + if cmdName == "goredo" { + symlinks = flag.Bool("symlinks", false, "create necessary symlinks in current directory") + } + + flag.Usage = func() { usage(os.Args[0]) } flag.Parse() if *warranty { fmt.Println(Warranty) return } if *version { - fmt.Println(versionGet()) + fmt.Println("goredo", Version, "built with", runtime.Version()) return } - if *symlinks { + if cmdName == CmdNameGoredo && *symlinks { rc := 0 for _, cmdName := range []string{ - "redo", - "redo-always", - "redo-cleanup", - "redo-dot", - "redo-ifchange", - "redo-ifcreate", - "redo-log", - "redo-stamp", - "redo-whichdo", + CmdNameRedo, + CmdNameRedoAffects, + CmdNameRedoAlways, + CmdNameRedoCleanup, + CmdNameRedoDot, + CmdNameRedoIfchange, + CmdNameRedoIfcreate, + CmdNameRedoLog, + CmdNameRedoOOD, + CmdNameRedoSources, + CmdNameRedoStamp, + CmdNameRedoTargets, + CmdNameRedoWhichdo, } { fmt.Println(os.Args[0], "<-", cmdName) if err := os.Symlink(os.Args[0], cmdName); err != nil { @@ -92,16 +130,17 @@ func main() { } os.Exit(rc) } - log.SetFlags(0) + log.SetFlags(log.Lshortfile) var err error Cwd, err = os.Getwd() if err != nil { - panic(err) + log.Fatalln(err) } NoColor = os.Getenv(EnvNoColor) != "" NoSync = os.Getenv(EnvNoSync) == "1" + InodeTrust = os.Getenv(EnvInodeNoTrust) == "" TopDir = os.Getenv(EnvTopDir) if TopDir == "" { @@ -113,30 +152,31 @@ func main() { } } DirPrefix = os.Getenv(EnvDirPrefix) + DepCwd = os.Getenv(EnvDepCwd) - if *flagStderrKeep { - mustSetenv(EnvStderrKeep, "1") + if flagStderrKeep != nil && *flagStderrKeep { + mustSetenv(EnvStderrKeep) } - if *flagStderrSilent { - mustSetenv(EnvStderrSilent, "1") + if flagStderrSilent != nil && *flagStderrSilent { + mustSetenv(EnvStderrSilent) } - if *flagNoProgress { - mustSetenv(EnvNoProgress, "1") + if flagNoProgress != nil && *flagNoProgress { + mustSetenv(EnvNoProgress) } - if *flagDebug { - mustSetenv(EnvDebug, "1") + if flagDebug != nil && *flagDebug { + mustSetenv(EnvDebug) } - if *flagLogWait { - mustSetenv(EnvLogWait, "1") + if flagLogWait != nil && *flagLogWait { + mustSetenv(EnvLogWait) } - if *flagLogLock { - mustSetenv(EnvLogLock, "1") + if flagLogLock != nil && *flagLogLock { + mustSetenv(EnvLogLock) } - if *flagLogPid { - mustSetenv(EnvLogPid, "1") + if flagLogPid != nil && *flagLogPid { + mustSetenv(EnvLogPid) } - if *flagLogJS { - mustSetenv(EnvLogJS, "1") + if flagLogJS != nil && *flagLogJS { + mustSetenv(EnvLogJS) } StderrKeep = os.Getenv(EnvStderrKeep) == "1" StderrSilent = os.Getenv(EnvStderrSilent) == "1" @@ -149,16 +189,59 @@ func main() { MyPid = os.Getpid() } var traced bool - if *flagTraceAll { - mustSetenv(EnvTrace, "1") + if flagTraceAll != nil && *flagTraceAll { + mustSetenv(EnvTrace) } - if *flagTrace { + if os.Getenv(EnvTrace) == "1" { + TracedAll = true traced = true - } else { - traced = os.Getenv(EnvTrace) == "1" + } else if flagTrace != nil { + traced = *flagTrace } // Those are internal envs + FdOODTgts, err = ioutil.TempFile("", "ood-tgts") + if err != nil { + log.Fatalln(err) + } + if err = os.Remove(FdOODTgts.Name()); err != nil { + log.Fatalln(err) + } + FdOODTgtsLock, err = ioutil.TempFile("", "ood-tgts.lock") + if err != nil { + log.Fatalln(err) + } + if err = os.Remove(FdOODTgtsLock.Name()); err != nil { + log.Fatalln(err) + } + + if v := os.Getenv(EnvOODTgtsFd); v != "" { + fd := mustParseFd(v, EnvOODTgtsFd) + fdLock := mustParseFd(v, EnvOODTgtsLockFd) + if err = unix.Flock(int(fdLock.Fd()), unix.LOCK_EX); err != nil { + log.Fatalln(err) + } + if _, err = fd.Seek(0, io.SeekStart); err != nil { + log.Fatalln(err) + } + tgtsRaw, err := ioutil.ReadAll(bufio.NewReader(fd)) + if err != nil { + log.Fatalln(err) + } + if err = unix.Flock(int(fdLock.Fd()), unix.LOCK_UN); err != nil { + log.Fatalln(err) + } + OODTgts = map[string]struct{}{} + for _, tgtRaw := range bytes.Split(tgtsRaw, []byte{0}) { + t := string(tgtRaw) + if t == "" { + continue + } + OODTgts[t] = struct{}{} + tracef(CDebug, "ood: known to be: %s", t) + } + } + StderrPrefix = os.Getenv(EnvStderrPrefix) if v := os.Getenv(EnvLevel); v != "" { Level, err = strconv.Atoi(v) @@ -169,27 +252,34 @@ func main() { panic("negative " + EnvLevel) } } + var fdDep *os.File if v := os.Getenv(EnvDepFd); v != "" { fdDep = mustParseFd(v, EnvDepFd) } - BuildUUID = os.Getenv(EnvBuildUUID) + tgts := flag.Args() + BuildUUID = os.Getenv(EnvBuildUUID) + tgtsWasEmpty := len(tgts) == 0 if BuildUUID == "" { + IsTopRedo = true raw := new([16]byte) if _, err = io.ReadFull(rand.Reader, raw[:]); err != nil { - panic(err) + log.Fatalln(err) } raw[6] = (raw[6] & 0x0F) | uint8(4<<4) // version 4 BuildUUID = fmt.Sprintf( "%x-%x-%x-%x-%x", raw[0:4], raw[4:6], raw[6:8], raw[8:10], raw[10:], ) - if len(tgts) == 0 { + if tgtsWasEmpty { tgts = []string{"all"} } } - statusInit() + + if cmdName == CmdNameRedo || cmdName == CmdNameRedoIfchange { + statusInit() + } for i, tgt := range tgts { if path.IsAbs(tgt) { @@ -197,85 +287,101 @@ func main() { } } + killed := make(chan os.Signal, 1) + signal.Notify(killed, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) + go func() { + <-killed + tracef(CDebug, "[%s] killed", BuildUUID) + jsReleaseAll() + RunningProcsM.Lock() + for pid, proc := range RunningProcs { + tracef(CDebug, "[%s] killing child %d", BuildUUID, pid) + _ = proc.Signal(syscall.SIGTERM) + } + os.Exit(1) + }() + ok := true err = nil - cmdName := path.Base(os.Args[0]) - trace( + tracef( CDebug, "[%s] run: %s %s cwd:%s dirprefix:%s", BuildUUID, cmdName, tgts, Cwd, DirPrefix, ) CmdSwitch: switch cmdName { - case "redo": + case CmdNameRedo: for _, tgt := range tgts { ok, err = ifchange([]string{tgt}, true, traced) if err != nil || !ok { break } } - case "redo-ifchange": + case CmdNameRedoIfchange: ok, err = ifchange(tgts, false, traced) if err == nil { err = writeDeps(fdDep, tgts) } - case "redo-ifcreate": + case CmdNameRedoIfcreate: if fdDep == nil { log.Fatalln("no", EnvDepFd) } for _, tgt := range tgts { - err = ifcreate(fdDep, tgt) + tgtRel, err := filepath.Rel( + filepath.Join(Cwd, DirPrefix), + filepath.Join(Cwd, tgt), + ) + if err != nil { + break + } + err = ifcreate(fdDep, tgtRel) if err != nil { break } } - case "redo-always": + case CmdNameRedoAlways: if fdDep == nil { log.Fatalln("no", EnvDepFd) } err = always(fdDep) - case "redo-cleanup": + case CmdNameRedoCleanup: for _, what := range tgts { err = cleanupWalker(Cwd, what) if err != nil { break } } - case "redo-dot": + case CmdNameRedoDot: err = dotPrint(tgts) - case "redo-stamp": + case CmdNameRedoStamp: if fdDep == nil { log.Fatalln("no", EnvDepFd) } err = stamp(fdDep, os.Stdin) - case "redo-log": + case CmdNameRedoLog: if len(tgts) != 1 { log.Fatalln("single target expected") } d, t := cwdAndTgt(tgts[0]) - var fd *os.File - fd, err = os.Open(path.Join(d, RedoDir, t+LogSuffix)) - if err != nil { - break - } - _, err = io.Copy(os.Stdout, fd) - case "redo-whichdo": + err = showBuildLog(d, t, nil, 0) + case CmdNameRedoWhichdo: if len(tgts) != 1 { log.Fatalln("single target expected") } var fdTmp *os.File - fdTmp, err = ioutil.TempFile("", "") + fdTmp, err = ioutil.TempFile("", "whichdo") if err != nil { break } - os.Remove(fdTmp.Name()) + if err = os.Remove(fdTmp.Name()); err != nil { + break + } cwd, tgt := cwdAndTgt(tgts[0]) - cwdRel := cwdMustRel(cwd) doFile, upLevels, err := findDo(fdTmp, cwd, tgt) if err != nil { break } - _, err = fdTmp.Seek(0, 0) + _, err = fdTmp.Seek(0, io.SeekStart) if err != nil { break } @@ -283,7 +389,7 @@ CmdSwitch: for { m, err := r.NextMap() if err != nil { - if err == io.EOF { + if errors.Is(err, io.EOF) { break } break CmdSwitch @@ -293,7 +399,86 @@ CmdSwitch: if doFile == "" { ok = false } else { - fmt.Println(path.Join(cwdRel, strings.Repeat("..", upLevels), doFile)) + p := make([]string, 0, upLevels+2) + p = append(p, cwd) + for i := 0; i < upLevels; i++ { + p = append(p, "..") + } + p = append(p, doFile) + rel, err := filepath.Rel(Cwd, path.Join(p...)) + if err != nil { + panic(err) + } + fmt.Println(rel) + } + case CmdNameRedoTargets: + if tgtsWasEmpty { + tgts = []string{Cwd} + } + tgts, err = targetsWalker(tgts) + if err != nil { + break + } + sort.Strings(tgts) + for _, tgt := range tgts { + fmt.Println(tgt) + } + case CmdNameRedoAffects: + if tgtsWasEmpty { + log.Fatalln("no targets specified") + } + var tgtsKnown []string + tgtsKnown, err = targetsWalker([]string{Cwd}) + if err != nil { + break + } + deps := map[string]map[string]struct{}{} + for _, tgt := range tgtsKnown { + collectDeps(Cwd, tgt, 0, deps, true) + } + seen := map[string]struct{}{} + for _, tgt := range tgts { + collectWholeDeps(deps[tgt], deps, seen) + } + tgts := make([]string, 0, len(seen)) + for dep := range seen { + tgts = append(tgts, dep) + } + sort.Strings(tgts) + for _, dep := range tgts { + fmt.Println(dep) + } + case CmdNameRedoOOD: + if tgtsWasEmpty { + tgts, err = targetsWalker([]string{Cwd}) + if err != nil { + break + } + } + sort.Strings(tgts) + var ood bool + for _, tgt := range tgts { + ood, err = isOOD(Cwd, tgt, 0, nil) + if err != nil { + break + } + if ood { + fmt.Println(tgt) + } + } + case CmdNameRedoSources: + if tgtsWasEmpty { + tgts, err = targetsWalker([]string{Cwd}) + if err != nil { + break + } + } + sort.Strings(tgts) + var srcs []string + srcs, err = sourcesWalker(tgts) + sort.Strings(srcs) + for _, src := range srcs { + fmt.Println(src) } default: log.Fatalln("unknown command", cmdName) @@ -305,6 +490,6 @@ CmdSwitch: if !ok || err != nil { rc = 1 } - trace(CDebug, "[%s] finished: %s %s", BuildUUID, cmdName, tgts) + tracef(CDebug, "[%s] finished: %s %s", BuildUUID, cmdName, tgts) os.Exit(rc) }