/*
-goredo -- redo implementation on pure Go
-Copyright (C) 2020 Sergey Matveev <stargrave@stargrave.org>
+goredo -- djb's redo implementation on pure Go
+Copyright (C) 2020-2021 Sergey Matveev <stargrave@stargrave.org>
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
package main
import (
+ "bufio"
+ "bytes"
"crypto/rand"
"flag"
"fmt"
"os"
"path"
"path/filepath"
+ "runtime"
+ "sort"
"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"
+ 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 (
- 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
-
+ Cwd string
BuildUUID string
- IsMaster bool = false
+ IsTopRedo bool // is it the top redo instance
)
func mustSetenv(key, value string) {
return fd
}
-func ifchange(tgts []string) (bool, error) {
- jsInit()
- defer Jobs.Wait()
- defer jsAcquire()
- errs := make(chan error, len(tgts))
- jobs := 0
- ok := true
- var err error
- for _, tgt := range tgts {
- var ood bool
- if Force {
- ood = true
- } else {
- ood, err = isOOD(Cwd, tgt, 0)
- if err != nil {
- return false, err
- }
- }
- if !ood {
- continue
- }
- if isSrc(Cwd, tgt) {
- trace(CDebug, "%s is source", tgt)
- continue
- }
- if err = runScript(tgt, errs); err != nil {
- return false, err
- }
- if Force {
- // Sequentially run jobs
- err = <-errs
- Jobs.Wait()
- if err != nil {
- trace(CErr, "%s", err)
- return false, nil
- }
- continue
- }
- jobs++
- }
- for i := 0; i < jobs; i++ {
- if err = <-errs; err != nil {
- ok = false
- trace(CErr, "%s", err)
- }
- }
- return ok, nil
+func CmdName() string {
+ return path.Base(os.Args[0])
}
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)")
- logDone := flag.Bool("log-done", false, "enable done messages logging (REDO_LOG_DONE=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
+ 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-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 {
- log.Fatalln(err)
+ rc = 1
+ log.Println(err)
}
}
- return
+ os.Exit(rc)
}
- log.SetFlags(0)
+ log.SetFlags(log.Lshortfile)
var err error
- Cwd, err = unix.Getwd()
+ Cwd, err = os.Getwd()
if err != nil {
- panic(err)
+ log.Fatalln(err)
}
- NoColor = os.Getenv("NO_COLOR") != ""
- NoHash = os.Getenv("REDO_NO_HASH") == "1"
- NoSync = os.Getenv("REDO_NO_SYNC") == "1"
+ NoColor = os.Getenv(EnvNoColor) != ""
+ NoSync = os.Getenv(EnvNoSync) == "1"
+ InodeTrust = os.Getenv(EnvInodeNoTrust) == ""
- TopDir = os.Getenv("REDO_TOP_DIR")
- if TopDir != "" {
+ TopDir = os.Getenv(EnvTopDir)
+ if TopDir == "" {
+ TopDir = "/"
+ } else {
TopDir, err = filepath.Abs(TopDir)
if err != nil {
panic(err)
}
}
+ DirPrefix = os.Getenv(EnvDirPrefix)
+ DepCwd = os.Getenv(EnvDepCwd)
- if *stderrKeep {
- mustSetenv("REDO_STDERR_KEEP", "1")
+ if flagStderrKeep != nil && *flagStderrKeep {
+ mustSetenv(EnvStderrKeep, "1")
+ }
+ if flagStderrSilent != nil && *flagStderrSilent {
+ mustSetenv(EnvStderrSilent, "1")
}
- if *stderrSilent {
- mustSetenv("REDO_STDERR_SILENT", "1")
+ if flagNoProgress != nil && *flagNoProgress {
+ mustSetenv(EnvNoProgress, "1")
}
- if *debug {
- mustSetenv("REDO_DEBUG", "1")
+ if flagDebug != nil && *flagDebug {
+ mustSetenv(EnvDebug, "1")
}
- if *logDone {
- mustSetenv("REDO_LOG_DONE", "1")
+ if flagLogWait != nil && *flagLogWait {
+ mustSetenv(EnvLogWait, "1")
}
- if *logLock {
- mustSetenv("REDO_LOG_LOCK", "1")
+ if flagLogLock != nil && *flagLogLock {
+ mustSetenv(EnvLogLock, "1")
}
- if *logPid {
- mustSetenv("REDO_LOG_PID", "1")
+ if flagLogPid != nil && *flagLogPid {
+ mustSetenv(EnvLogPid, "1")
}
- if *logJS {
- mustSetenv("REDO_LOG_JS", "1")
+ if flagLogJS != nil && *flagLogJS {
+ mustSetenv(EnvLogJS, "1")
}
- StderrKeep = os.Getenv("REDO_STDERR_KEEP") == "1"
- StderrSilent = os.Getenv("REDO_STDERR_SILENT") == "1"
- Debug = os.Getenv("REDO_DEBUG") == "1"
- LogDone = os.Getenv("REDO_LOG_DONE") == "1"
- LogLock = os.Getenv("REDO_LOG_LOCK") == "1"
- LogJS = os.Getenv("REDO_LOG_JS") == "1"
- if Debug || os.Getenv("REDO_LOG_PID") == "1" {
+ StderrKeep = os.Getenv(EnvStderrKeep) == "1"
+ StderrSilent = os.Getenv(EnvStderrSilent) == "1"
+ NoProgress = os.Getenv(EnvNoProgress) == "1"
+ Debug = os.Getenv(EnvDebug) == "1"
+ LogWait = os.Getenv(EnvLogWait) == "1"
+ LogLock = os.Getenv(EnvLogLock) == "1"
+ LogJS = os.Getenv(EnvLogJS) == "1"
+ if Debug || os.Getenv(EnvLogPid) == "1" {
MyPid = os.Getpid()
}
- if *xflag {
- Trace = true
- } else {
- Trace = os.Getenv("REDO_TRACE") == "1"
+ var traced bool
+ if flagTraceAll != nil && *flagTraceAll {
+ mustSetenv(EnvTrace, "1")
+ }
+ if os.Getenv(EnvTrace) == "1" {
+ TracedAll = true
+ traced = true
+ } else if flagTrace != nil {
+ traced = *flagTrace
}
// Those are internal envs
- StderrPrefix = os.Getenv(RedoStderrPrefixEnv)
- if v := os.Getenv(RedoLevelEnv); v != "" {
- level, err := strconv.ParseUint(v, 10, 64)
+ 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)
+ }
+ unix.Flock(int(fdLock.Fd()), unix.LOCK_UN)
+ OODTgts = map[string]struct{}{}
+ for _, tgtRaw := range bytes.Split(tgtsRaw, []byte{0}) {
+ t := string(tgtRaw)
+ if t == "" {
+ continue
+ }
+ OODTgts[t] = struct{}{}
+ trace(CDebug, "ood: known to be: %s", t)
+ }
+ }
+
+ StderrPrefix = os.Getenv(EnvStderrPrefix)
+ if v := os.Getenv(EnvLevel); v != "" {
+ Level, err = strconv.Atoi(v)
if err != nil {
panic(err)
}
- Level = int(level)
+ if Level < 0 {
+ panic("negative " + EnvLevel)
+ }
}
+
var fdDep *os.File
- if v := os.Getenv(RedoDepFdEnv); v != "" {
- fdDep = mustParseFd(v, RedoDepFdEnv)
+ if v := os.Getenv(EnvDepFd); v != "" {
+ fdDep = mustParseFd(v, EnvDepFd)
}
- BuildUUID = os.Getenv(RedoBuildUUIDEnv)
+
+ tgts := flag.Args()
+ BuildUUID = os.Getenv(EnvBuildUUID)
+ tgtsWasEmpty := len(tgts) == 0
if BuildUUID == "" {
- IsMaster = true
+ 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 tgtsWasEmpty {
+ tgts = []string{"all"}
+ }
}
- tgts := flag.Args()
- if len(tgts) == 0 {
- tgts = []string{"all"}
+ if cmdName == CmdNameRedo || cmdName == CmdNameRedoIfchange {
+ statusInit()
+ }
+
+ for i, tgt := range tgts {
+ if path.IsAbs(tgt) {
+ tgts[i] = cwdMustRel(tgt)
+ }
}
+
ok := true
err = nil
- cmdName := path.Base(os.Args[0])
- trace(CDebug, "[%s] run: %s %s [%s]", BuildUUID, cmdName, tgts, Cwd)
+ trace(
+ CDebug, "[%s] run: %s %s cwd:%s dirprefix:%s",
+ BuildUUID, cmdName, tgts, Cwd, DirPrefix,
+ )
CmdSwitch:
switch cmdName {
- case "redo":
- Force = true
- ok, err = ifchange(tgts)
- case "redo-ifchange":
- ok, err = ifchange(tgts)
- writeDeps(fdDep, tgts)
- case "redo-ifcreate":
+ case CmdNameRedo:
+ for _, tgt := range tgts {
+ ok, err = ifchange([]string{tgt}, true, traced)
+ if err != nil || !ok {
+ break
+ }
+ }
+ case CmdNameRedoIfchange:
+ ok, err = ifchange(tgts, false, traced)
+ if err == nil {
+ err = writeDeps(fdDep, tgts)
+ }
+ case CmdNameRedoIfcreate:
if fdDep == nil {
- log.Fatalln("no", RedoDepFdEnv)
+ log.Fatalln("no", EnvDepFd)
}
for _, tgt := range tgts {
err = ifcreate(fdDep, tgt)
break
}
}
- case "redo-always":
+ case CmdNameRedoAlways:
if fdDep == nil {
- log.Fatalln("no", RedoDepFdEnv)
+ 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-stamp":
+ case CmdNameRedoDot:
+ err = dotPrint(tgts)
+ case CmdNameRedoStamp:
if fdDep == nil {
- log.Fatalln("no", RedoDepFdEnv)
+ 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())
- var doFile string
+ if err = os.Remove(fdTmp.Name()); err != nil {
+ break
+ }
cwd, tgt := cwdAndTgt(tgts[0])
- doFile, _, err = findDo(fdTmp, cwd, tgt)
+ doFile, upLevels, err := findDo(fdTmp, cwd, tgt)
+ if err != nil {
+ break
+ }
+ _, err = fdTmp.Seek(0, io.SeekStart)
if err != nil {
break
}
- fdTmp.Seek(0, 0)
r := recfile.NewReader(fdTmp)
for {
m, err := r.NextMap()
}
break CmdSwitch
}
- rel, err := filepath.Rel(Cwd, path.Join(cwd, m["Target"]))
+ fmt.Println(cwdMustRel(cwd, m["Target"]))
+ }
+ if doFile == "" {
+ ok = false
+ } else {
+ 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)
}
- ok = doFile != ""
+ 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)
}
if err != nil {
log.Println(err)
}
+ rc := 0
if !ok || err != nil {
- os.Exit(1)
+ rc = 1
}
+ trace(CDebug, "[%s] finished: %s %s", BuildUUID, cmdName, tgts)
+ os.Exit(rc)
}