/* goredo -- redo implementation on pure Go Copyright (C) 2020 Sergey Matveev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package main import ( "crypto/rand" "flag" "fmt" "io" "io/ioutil" "log" "os" "path" "path/filepath" "strconv" "sync" "go.cypherpunks.ru/recfile" "golang.org/x/sys/unix" ) const ( RedoLevelEnv = "REDO_LEVEL" RedoDepFdEnv = "REDO_DEP_FD" RedoDirPrefixEnv = "REDO_DIRPREFIX" RedoBuildUUIDEnv = "REDO_BUILD_UUID" RedoStderrPrefixEnv = "REDO_STDERR_PREFIX" RedoJSRFdEnv = "REDO_JSR_FD" RedoJSWFdEnv = "REDO_JSW_FD" ) var ( Level = 0 Trace bool = false Force bool = false NoHash bool = false NoSync bool = false Cwd string Jobs sync.WaitGroup JobsN = flag.Uint("j", 1, "number of parallel jobs (0 for infinite)") StderrKeep bool = false StderrSilent bool = false StderrPrefix string BuildUUID string IsMaster bool = false ) func mustSetenv(key, value string) { if err := os.Setenv(key, value); err != nil { panic(err) } } func mustParseFd(v, name string) *os.File { ptr, err := strconv.ParseUint(v, 10, 64) if err != nil { panic(err) } fd := os.NewFile(uintptr(ptr), name) if fd == nil { panic("can not parse fd: " + name) } return fd } func main() { xflag := flag.Bool("x", false, "trace current target (sh -x) (set REDO_TRACE=1 for others too)") stderrKeep := flag.Bool("stderr-keep", false, "keep job's stderr (REDO_STDERR_KEEP=1)") stderrSilent := flag.Bool("stderr-silent", false, "do not print job's stderr (REDO_STDERR_SILENT=1)") debug := flag.Bool("debug", false, "enable debug logging (REDO_DEBUG=1)") logWait := flag.Bool("log-wait", false, "enable wait messages logging (REDO_LOG_WAIT=1)") logLock := flag.Bool("log-lock", false, "enable lock messages logging (REDO_LOG_LOCK=1)") logPid := flag.Bool("log-pid", false, "append PIDs (REDO_LOG_PID=1)") logJS := flag.Bool("log-js", false, "enable jobserver messages logging (REDO_LOG_JS=1)") 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 direcotyr") flag.Usage = usage flag.Parse() if *warranty { fmt.Println(Warranty) return } if *version { fmt.Println(versionGet()) return } if *symlinks { for _, cmdName := range []string{ "redo", "redo-always", "redo-cleanup", "redo-ifchange", "redo-ifcreate", "redo-log", "redo-stamp", "redo-whichdo", } { if err := os.Symlink(os.Args[0], cmdName); err != nil { log.Fatalln(err) } } return } log.SetFlags(0) var err error Cwd, err = unix.Getwd() if err != nil { panic(err) } NoColor = os.Getenv("NO_COLOR") != "" NoHash = os.Getenv("REDO_NO_HASH") == "1" NoSync = os.Getenv("REDO_NO_SYNC") == "1" TopDir = os.Getenv("REDO_TOP_DIR") if TopDir != "" { TopDir, err = filepath.Abs(TopDir) if err != nil { panic(err) } } if *stderrKeep { mustSetenv("REDO_STDERR_KEEP", "1") } if *stderrSilent { mustSetenv("REDO_STDERR_SILENT", "1") } if *debug { mustSetenv("REDO_DEBUG", "1") } if *logWait { mustSetenv("REDO_LOG_WAIT", "1") } if *logLock { mustSetenv("REDO_LOG_LOCK", "1") } if *logPid { mustSetenv("REDO_LOG_PID", "1") } if *logJS { mustSetenv("REDO_LOG_JS", "1") } StderrKeep = os.Getenv("REDO_STDERR_KEEP") == "1" StderrSilent = os.Getenv("REDO_STDERR_SILENT") == "1" Debug = os.Getenv("REDO_DEBUG") == "1" LogWait = os.Getenv("REDO_LOG_WAIT") == "1" LogLock = os.Getenv("REDO_LOG_LOCK") == "1" LogJS = os.Getenv("REDO_LOG_JS") == "1" if Debug || os.Getenv("REDO_LOG_PID") == "1" { MyPid = os.Getpid() } if *xflag { Trace = true } else { Trace = os.Getenv("REDO_TRACE") == "1" } // Those are internal envs StderrPrefix = os.Getenv(RedoStderrPrefixEnv) if v := os.Getenv(RedoLevelEnv); v != "" { level, err := strconv.ParseUint(v, 10, 64) if err != nil { panic(err) } Level = int(level) } var fdDep *os.File if v := os.Getenv(RedoDepFdEnv); v != "" { fdDep = mustParseFd(v, RedoDepFdEnv) } BuildUUID = os.Getenv(RedoBuildUUIDEnv) if BuildUUID == "" { IsMaster = true raw := new([16]byte) if _, err = io.ReadFull(rand.Reader, raw[:]); err != nil { panic(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:], ) } tgts := flag.Args() if len(tgts) == 0 { tgts = []string{"all"} } ok := true err = nil cmdName := path.Base(os.Args[0]) trace(CDebug, "[%s] run: %s %s [%s]", BuildUUID, cmdName, tgts, Cwd) CmdSwitch: switch cmdName { case "redo": Force = true ok, err = ifchange(tgts) case "redo-ifchange": ok, err = ifchange(tgts) writeDeps(fdDep, tgts) case "redo-ifcreate": if fdDep == nil { log.Fatalln("no", RedoDepFdEnv) } for _, tgt := range tgts { err = ifcreate(fdDep, tgt) if err != nil { break } } case "redo-always": if fdDep == nil { log.Fatalln("no", RedoDepFdEnv) } err = always(fdDep) case "redo-cleanup": for _, what := range tgts { err = cleanupWalker(Cwd, what) if err != nil { break } } case "redo-stamp": if fdDep == nil { log.Fatalln("no", RedoDepFdEnv) } err = stamp(fdDep, os.Stdin) case "redo-log": 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": if len(tgts) != 1 { log.Fatalln("single target expected") } var fdTmp *os.File fdTmp, err = ioutil.TempFile("", "") if err != nil { break } os.Remove(fdTmp.Name()) var doFile string cwd, tgt := cwdAndTgt(tgts[0]) doFile, _, err = findDo(fdTmp, cwd, tgt) if err != nil { break } fdTmp.Seek(0, 0) r := recfile.NewReader(fdTmp) for { m, err := r.NextMap() if err != nil { if err == io.EOF { break } break CmdSwitch } rel, err := filepath.Rel(Cwd, path.Join(cwd, m["Target"])) if err != nil { panic(err) } fmt.Println(rel) } ok = doFile != "" default: log.Fatalln("unknown command", cmdName) } if err != nil { log.Println(err) } rc := 0 if !ok || err != nil { rc = 1 } trace(CDebug, "[%s] finished: %s %s", BuildUUID, cmdName, tgts) os.Exit(rc) }