X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=run.go;h=4ca90d649af939e151326ea2636f65cd59e6f3bc;hb=5acd5b140240cd8510a2d22ccf419e4654b48a64;hp=e40ad1d5d5e4b65a262ea385b4a0e10df3274fa6;hpb=b2eef1c806af5802089517b8c86abc65edfe574d;p=goredo.git diff --git a/run.go b/run.go index e40ad1d..4ca90d6 100644 --- a/run.go +++ b/run.go @@ -1,6 +1,6 @@ /* -goredo -- redo implementation on pure Go -Copyright (C) 2020 Sergey Matveev +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 it under the terms of the GNU General Public License as published by @@ -21,7 +21,6 @@ package main import ( "bufio" - "encoding/hex" "errors" "flag" "fmt" @@ -29,12 +28,14 @@ import ( "os" "os/exec" "path" + "path/filepath" "strings" "sync" "syscall" "time" "go.cypherpunks.ru/recfile" + "go.cypherpunks.ru/tai64n/v2" "golang.org/x/sys/unix" ) @@ -50,7 +51,7 @@ const ( RedoDir = ".redo" LockSuffix = ".lock" - DepSuffix = ".dep" + DepSuffix = ".rec" TmpPrefix = ".redo." LogSuffix = ".log" ) @@ -62,9 +63,12 @@ var ( StderrPrefix string Jobs sync.WaitGroup - flagTrace = flag.Bool("x", false, fmt.Sprintf("trace current target (sh -x) (set %s=1 for all others)", EnvTrace)) + flagTrace = flag.Bool("x", false, "trace (sh -x) current targets") + flagTraceAll = flag.Bool("xx", false, fmt.Sprintf("trace (sh -x) all targets (%s=1)", EnvTrace)) flagStderrKeep = flag.Bool("logs", false, fmt.Sprintf("keep job's stderr (%s=1)", EnvStderrKeep)) flagStderrSilent = flag.Bool("silent", false, fmt.Sprintf("do not print job's stderr (%s=1)", EnvStderrSilent)) + + TracedAll bool ) type RunErr struct { @@ -75,8 +79,6 @@ type RunErr struct { Err error } -func (e RunErr) Unwrap() error { return e.Err } - func (e *RunErr) Name() string { var name string if e.DoFile == "" { @@ -87,7 +89,7 @@ func (e *RunErr) Name() string { if e.Finished == nil { return name } - return fmt.Sprintf("%s (%fsec)", name, e.Finished.Sub(*e.Started).Seconds()) + return fmt.Sprintf("%s (%.3fs)", name, e.Finished.Sub(*e.Started).Seconds()) } func (e RunErr) Error() string { @@ -101,24 +103,24 @@ func mkdirs(pth string) error { return os.MkdirAll(pth, os.FileMode(0777)) } -func isModified(cwd, redoDir, tgt string) (bool, string, error) { +func isModified(cwd, redoDir, tgt string) (bool, *Inode, error) { fdDep, err := os.Open(path.Join(redoDir, tgt+DepSuffix)) if err != nil { if os.IsNotExist(err) { - return false, "", nil + return false, nil, nil } - return false, "", err + return false, nil, err } defer fdDep.Close() r := recfile.NewReader(fdDep) - var ourTs string + var ourInode *Inode for { m, err := r.NextMap() if err != nil { if err == io.EOF { break } - return false, "", err + return false, nil, err } if m["Target"] != tgt { continue @@ -126,21 +128,25 @@ func isModified(cwd, redoDir, tgt string) (bool, string, error) { fd, err := os.Open(path.Join(cwd, tgt)) if err != nil { if os.IsNotExist(err) { - return false, "", nil + return false, nil, nil } - return false, "", err + return false, nil, err } - defer fd.Close() - ourTs, err = fileCtime(fd) + ourInode, err = inodeFromFile(fd) + fd.Close() if err != nil { - return false, "", err + return false, nil, err } - if ourTs != m["Ctime"] { - return true, ourTs, nil + theirInode, err := inodeFromRec(m) + if err != nil { + return false, nil, err + } + if !ourInode.Equals(theirInode) { + return true, ourInode, nil } break } - return false, ourTs, nil + return false, ourInode, nil } func syncDir(dir string) error { @@ -200,7 +206,7 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { fdDep, err := os.Open(path.Join(redoDir, tgt+DepSuffix)) if err != nil { if os.IsNotExist(err) { - err = errors.New("was not built: no .dep") + err = errors.New("was not built: no .rec") } goto Finish } @@ -222,7 +228,7 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { } // Check if target is not modified externally - modified, tsPrev, err := isModified(cwd, redoDir, tgt) + modified, inodePrev, err := isModified(cwd, redoDir, tgt) if err != nil { lockRelease() return TgtErr{tgtOrig, err} @@ -236,12 +242,13 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { return nil } - // Start preparing .dep + // Start preparing .rec fdDep, err := tempfile(redoDir, tgt+DepSuffix) if err != nil { lockRelease() return TgtErr{tgtOrig, err} } + fdDepPath := fdDep.Name() cleanup := func() { lockRelease() fdDep.Close() @@ -266,12 +273,14 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { } // Determine basename and DIRPREFIX + doFileRelPath := doFile ents := strings.Split(cwd, "/") ents = ents[len(ents)-upLevels:] dirPrefix := path.Join(ents...) cwdOrig := cwd for i := 0; i < upLevels; i++ { cwd = path.Join(cwd, "..") + doFileRelPath = path.Join("..", doFileRelPath) } cwd = path.Clean(cwd) doFilePath := path.Join(cwd, doFile) @@ -279,13 +288,14 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { runErr := RunErr{Tgt: tgtOrig} if strings.HasPrefix(doFile, "default.") { basename = tgt[:len(tgt)-(len(doFile)-len("default.")-len(".do"))-1] - runErr.DoFile = doFile + runErr.DoFile = doFileRelPath } - if err = writeDep(fdDep, cwd, doFile); err != nil { + if err = writeDep(fdDep, cwdOrig, doFileRelPath); err != nil { cleanup() return TgtErr{tgtOrig, err} } + fdDep.Close() trace(CWait, "%s", runErr.Name()) // Prepare command line @@ -296,7 +306,7 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { args = make([]string, 0, 3) } else { cmdName = "/bin/sh" - if traced { + if traced || TracedAll { args = append(args, "-ex") } else { args = append(args, "-e") @@ -305,17 +315,28 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { } // Temporary file for stdout - fdStdout, err := tempfile(cwd, tgt) + fdStdout, err := tempfile(cwdOrig, tgt) if err != nil { cleanup() return TgtErr{tgtOrig, err} } - tmpPath := fdStdout.Name() + ".3" // and for $3 - args = append(args, tgt, basename, path.Base(tmpPath)) + stdoutPath := fdStdout.Name() + fdStdout.Close() + tmpPath := stdoutPath + ".3" // and for $3 + tmpPathRel, err := filepath.Rel(cwd, tmpPath) + if err != nil { + panic(err) + } + args = append( + args, + path.Join(dirPrefix, tgt), + path.Join(dirPrefix, basename), + tmpPathRel, + ) cmd := exec.Command(cmdName, args...) cmd.Dir = cwd - cmd.Stdout = fdStdout + // cmd.Stdin reads from /dev/null by default cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%d", EnvLevel, Level+1)) cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", EnvDirPrefix, dirPrefix)) cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", EnvBuildUUID, BuildUUID)) @@ -326,8 +347,11 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { )) fdNum := 0 - cmd.ExtraFiles = append(cmd.ExtraFiles, fdDep) - cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", EnvDepFd, 3+fdNum)) + cmd.ExtraFiles = append(cmd.ExtraFiles, FdOODTgts) + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", EnvOODTgtsFd, 3+fdNum)) + fdNum++ + cmd.ExtraFiles = append(cmd.ExtraFiles, FdOODTgtsLock) + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", EnvOODTgtsLockFd, 3+fdNum)) fdNum++ if FdStatus == nil { @@ -351,10 +375,6 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { } // Preparing stderr - stderr, err := cmd.StderrPipe() - if err != nil { - panic(err) - } var fdStderr *os.File if StderrKeep { fdStderr, err = os.OpenFile( @@ -368,7 +388,10 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { } fdStderr.Truncate(0) } - shCtx := fmt.Sprintf("sh: %s: %s %s [%s]", tgtOrig, cmdName, args, cwd) + shCtx := fmt.Sprintf( + "sh: %s: %s %s cwd:%s dirprefix:%s", + tgtOrig, cmdName, args, cwd, dirPrefix, + ) trace(CDebug, "%s", shCtx) Jobs.Add(1) @@ -377,6 +400,26 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { if FdStatus != nil { FdStatus.Write([]byte{StatusRun}) } + + started := time.Now() + runErr.Started = &started + fdStdout, err = os.OpenFile(stdoutPath, os.O_RDWR, os.FileMode(0666)) + if err != nil { + runErr.Err = err + errs <- runErr + return + } + cmd.Stdout = fdStdout + fdDep, err = os.OpenFile(fdDepPath, os.O_WRONLY|os.O_APPEND, os.FileMode(0666)) + if err != nil { + runErr.Err = err + errs <- runErr + return + } + cmd.ExtraFiles = append(cmd.ExtraFiles, fdDep) + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", EnvDepFd, 3+fdNum)) + fdNum++ + defer func() { jsRelease(shCtx) lockRelease() @@ -394,9 +437,14 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { } Jobs.Done() }() - started := time.Now() - runErr.Started = &started - err := cmd.Start() + stderr, err := cmd.StderrPipe() + if err != nil { + runErr.Err = err + errs <- runErr + return + } + started = time.Now() + err = cmd.Start() if err != nil { runErr.Err = err errs <- runErr @@ -409,7 +457,7 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { go func() { scanner := bufio.NewScanner(stderr) var line string - ts := new(TAI64N) + ts := new(tai64n.TAI64N) for scanner.Scan() { line = scanner.Text() if strings.HasPrefix(line, childStderrPrefix) { @@ -418,9 +466,9 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { continue } if fdStderr != nil { - tai64nNow(ts) + ts.FromTime(time.Now()) LogMutex.Lock() - fmt.Fprintf(fdStderr, "@%s %s\n", hex.EncodeToString(ts[:]), line) + fmt.Fprintln(fdStderr, tai64n.Encode(ts[:]), line) LogMutex.Unlock() } if StderrSilent { @@ -447,14 +495,15 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { } // Was $1 touched? - if fd, err := os.Open(path.Join(cwdOrig, tgt)); err == nil { - ts, err := fileCtime(fd) - fd.Close() - if err == nil && ts != tsPrev { - runErr.Err = errors.New("$1 was explicitly touched") - errs <- runErr + if inodePrev != nil { + if fd, err := os.Open(path.Join(cwdOrig, tgt)); err == nil { + inode, err := inodeFromFile(fd) fd.Close() - return + if err == nil && !inode.Equals(inodePrev) { + runErr.Err = errors.New("$1 was explicitly touched") + errs <- runErr + return + } } } @@ -494,6 +543,7 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { // Do we need to ifcreate it, of ifchange with renaming? if fd == nil { + os.Remove(path.Join(cwdOrig, tgt)) err = ifcreate(fdDep, tgt) if err != nil { goto Finish @@ -521,7 +571,7 @@ func runScript(tgtOrig string, errs chan error, traced bool) error { } } - // Commit .dep + // Commit .rec if !NoSync { err = fdDep.Sync() if err != nil {