-/*
-goredo -- djb's redo implementation on pure Go
-Copyright (C) 2020-2023 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
-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 <http://www.gnu.org/licenses/>.
-*/
+// goredo -- djb's redo implementation on pure Go
+// Copyright (C) 2020-2024 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
+// 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 <http://www.gnu.org/licenses/>.
package main
import (
"bufio"
"bytes"
- "crypto/rand"
+ "encoding/hex"
"errors"
"flag"
"fmt"
"io"
+ "io/fs"
"log"
"os"
"os/signal"
"strconv"
"syscall"
+ "github.com/google/uuid"
"go.cypherpunks.ru/recfile"
"golang.org/x/sys/unix"
)
CmdNameRedoAffects = "redo-affects"
CmdNameRedoAlways = "redo-always"
CmdNameRedoCleanup = "redo-cleanup"
+ CmdNameRedoDep2Rec = "redo-dep2rec"
CmdNameRedoDepFix = "redo-depfix"
CmdNameRedoDot = "redo-dot"
CmdNameRedoIfchange = "redo-ifchange"
var (
Cwd string
- BuildUUID string
+ BuildUUID uuid.UUID
IsTopRedo bool // is it the top redo instance
UmaskCur int
)
}
flag.Usage = func() { usage(os.Args[0]) }
- flag.Parse()
+
+ BuildUUIDStr := os.Getenv(EnvBuildUUID)
+ IsTopRedo = BuildUUIDStr == ""
+
+ var args []string
+ if IsTopRedo {
+ flag.Parse()
+ args = flag.Args()
+ } else {
+ args = os.Args[1:]
+ }
+
if *warranty {
fmt.Println(Warranty)
return
CmdNameRedoAffects,
CmdNameRedoAlways,
CmdNameRedoCleanup,
+ CmdNameRedoDep2Rec,
CmdNameRedoDepFix,
CmdNameRedoDot,
CmdNameRedoIfchange,
LogLock = os.Getenv(EnvLogLock) == "1"
LogJS = os.Getenv(EnvLogJS) == "1"
if Debug || os.Getenv(EnvLogPid) == "1" {
- MyPid = os.Getpid()
+ MyPID = os.Getpid()
}
var traced bool
if flagTraceAll != nil && *flagTraceAll {
fdDep = mustParseFd(v, EnvDepFd)
}
- tgts := make([]*Tgt, 0, len(flag.Args()))
- for _, arg := range flag.Args() {
+ tgts := make([]*Tgt, 0, len(args))
+ for _, arg := range args {
tgts = append(tgts, NewTgt(arg))
}
- 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 {
- log.Fatal(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 BuildUUIDStr == "" {
+ BuildUUID = uuid.New()
if tgtsWasEmpty {
tgts = append(tgts, NewTgt("all"))
}
tracef(CDebug, "inode-trust: %s", InodeTrust)
+ } else {
+ BuildUUID, err = uuid.Parse(BuildUUIDStr)
+ if err != nil {
+ log.Fatal(err)
+ }
}
if cmdName == CmdNameRedo {
tracef(CDebug, "[%s] run: %s %s cwd:%s dirprefix:%s",
BuildUUID, cmdName, tgts, Cwd, DirPrefix)
-CmdSwitch:
switch cmdName {
case CmdNameRedo:
for _, tgt := range tgts {
if fdDep == nil {
log.Fatalln("no", EnvDepFd)
}
+ fdDepW := bufio.NewWriter(fdDep)
for _, tgt := range tgts {
- err = ifcreate(fdDep, tgt.RelTo(path.Join(Cwd, DirPrefix)))
+ err = ifcreate(fdDepW, fdDep.Name(), tgt.RelTo(path.Join(Cwd, DirPrefix)))
if err != nil {
break
}
}
+ err = fdDepW.Flush()
case CmdNameRedoAlways:
if fdDep == nil {
log.Fatalln("no", EnvDepFd)
}
- err = always(fdDep)
+ err = always(fdDep, fdDep.Name())
case CmdNameRedoCleanup:
for _, what := range tgts {
- err = cleanupWalker(Cwd, what.t)
+ err = cleanupWalker(Cwd, path.Base(what.a))
if err != nil {
break
}
if fdDep == nil {
log.Fatalln("no", EnvDepFd)
}
- err = stamp(fdDep, os.Stdin)
+ var hsh Hash
+ hsh, err = fileHash(os.Stdin)
+ if err != nil {
+ break
+ }
+ err = stamp(fdDep, fdDep.Name(), hsh)
case CmdNameRedoLog:
if len(tgts) != 1 {
log.Fatal("single target expected")
if len(tgts) != 1 {
log.Fatal("single target expected")
}
- var fdTmp *os.File
- fdTmp, err = os.CreateTemp("", "whichdo")
- if err != nil {
- err = ErrLine(err)
- break
- }
- err = ErrLine(os.Remove(fdTmp.Name()))
+ var dos []string
+ dos, err = whichdo(tgts[0])
if err != nil {
- break
- }
- tgt := tgts[0]
- var doFile string
- var upLevels int
- doFile, upLevels, err = findDo(fdTmp, tgt.h, tgt.t)
- if err != nil {
- err = ErrLine(err)
- break
- }
- _, err = fdTmp.Seek(0, io.SeekStart)
- if err != nil {
- err = ErrLine(err)
- break
- }
- r := recfile.NewReader(fdTmp)
- for {
- m, err := r.NextMap()
- if err != nil {
- if errors.Is(err, io.EOF) {
- break
- }
- err = ErrLine(err)
- break CmdSwitch
- }
- fmt.Println(cwdMustRel(tgt.h, m["Target"]))
- }
- if doFile == "" {
- ok = false
- } else {
- p := make([]string, 0, upLevels+2)
- p = append(p, tgt.h)
- for i := 0; i < upLevels; i++ {
- p = append(p, "..")
+ if errors.Is(err, fs.ErrNotExist) {
+ err = nil
+ ok = false
+ } else {
+ break
}
- p = append(p, doFile)
- rel := mustRel(Cwd, path.Join(p...))
- fmt.Println(rel)
+ }
+ for _, do := range dos {
+ fmt.Println(do)
}
case CmdNameRedoTargets:
raws := make([]string, 0, len(tgts))
}
deps := make(map[string]map[string]*Tgt)
for _, tgt := range tgtsKnown {
- collectDeps(NewTgt(tgt), 0, deps, true, make(map[string]struct{}))
+ collectDeps(NewTgt(tgt), 0, deps, true)
}
seen := make(map[string]*Tgt)
for _, tgt := range tgts {
fmt.Println(src)
}
case CmdNameRedoDepFix:
+ IfchangeCache = nil
+ DepFixHashCache = make(map[string]Hash)
err = depFix(Cwd)
case CmdNameRedoInode:
var inode *Inode
break
}
}
+ case CmdNameRedoDep2Rec:
+ var data []byte
+ data, err = os.ReadFile(tgts[0].a)
+ if err != nil {
+ break
+ }
+ var build uuid.UUID
+ build, data, err = depHeadParse(data)
+ if err != nil {
+ break
+ }
+ w := bufio.NewWriter(os.Stdout)
+ err = recfileWrite(w, []recfile.Field{
+ {Name: "Build", Value: build.String()},
+ }...)
+ if err != nil {
+ break
+ }
+ var typ byte
+ var chunk []byte
+ var inode Inode
+ for len(data) > 0 {
+ typ, chunk, data, _ = chunkRead(data)
+ switch typ {
+ case DepTypeAlways:
+ err = recfileWrite(w, []recfile.Field{
+ {Name: "Type", Value: "always"},
+ }...)
+ case DepTypeStamp:
+ err = recfileWrite(w, []recfile.Field{
+ {Name: "Type", Value: "stamp"},
+ {Name: "Hash", Value: hex.EncodeToString(chunk)},
+ }...)
+ case DepTypeIfcreate:
+ err = recfileWrite(w, []recfile.Field{
+ {Name: "Type", Value: "ifcreate"},
+ {Name: "Target", Value: string(chunk)},
+ }...)
+ case DepTypeIfchange:
+ name := string(chunk[InodeLen+HashLen:])
+ meta := chunk[:InodeLen+HashLen]
+ fields := []recfile.Field{
+ {Name: "Type", Value: "ifchange"},
+ {Name: "Target", Value: name},
+ }
+ fields = append(fields, recfile.Field{
+ Name: "Hash", Value: Hash(meta[InodeLen:]).String(),
+ })
+ inode = Inode(meta[:][:InodeLen])
+ fields = append(fields, inode.RecfileFields()...)
+ err = recfileWrite(w, fields...)
+ case DepTypeIfchangeNonex:
+ err = recfileWrite(w, []recfile.Field{
+ {Name: "Type", Value: "ifchange"},
+ {Name: "Target", Value: string(chunk)},
+ }...)
+ }
+ if err != nil {
+ break
+ }
+ }
+ err = w.Flush()
default:
log.Fatalln("unknown command", cmdName)
}