]> Cypherpunks.ru repositories - goredo.git/blob - main.go
Fix whichdo regressions
[goredo.git] / main.go
1 /*
2 goredo -- djb's redo implementation on pure Go
3 Copyright (C) 2020-2023 Sergey Matveev <stargrave@stargrave.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 package main
19
20 import (
21         "bufio"
22         "bytes"
23         "encoding/hex"
24         "flag"
25         "fmt"
26         "io"
27         "log"
28         "os"
29         "os/signal"
30         "path"
31         "runtime"
32         "sort"
33         "strconv"
34         "syscall"
35
36         "github.com/google/uuid"
37         "go.cypherpunks.ru/recfile"
38         "golang.org/x/sys/unix"
39 )
40
41 const (
42         CmdNameGoredo       = "goredo"
43         CmdNameRedo         = "redo"
44         CmdNameRedoAffects  = "redo-affects"
45         CmdNameRedoAlways   = "redo-always"
46         CmdNameRedoCleanup  = "redo-cleanup"
47         CmdNameRedoDep2Rec  = "redo-dep2rec"
48         CmdNameRedoDepFix   = "redo-depfix"
49         CmdNameRedoDot      = "redo-dot"
50         CmdNameRedoIfchange = "redo-ifchange"
51         CmdNameRedoIfcreate = "redo-ifcreate"
52         CmdNameRedoInode    = "redo-inode"
53         CmdNameRedoLog      = "redo-log"
54         CmdNameRedoOOD      = "redo-ood"
55         CmdNameRedoSources  = "redo-sources"
56         CmdNameRedoStamp    = "redo-stamp"
57         CmdNameRedoTargets  = "redo-targets"
58         CmdNameRedoWhichdo  = "redo-whichdo"
59 )
60
61 var (
62         Cwd       string
63         BuildUUID uuid.UUID
64         IsTopRedo bool // is it the top redo instance
65         UmaskCur  int
66 )
67
68 func mustSetenv(key string) {
69         if err := os.Setenv(key, "1"); err != nil {
70                 panic(err)
71         }
72 }
73
74 func mustParseFd(v, name string) *os.File {
75         ptr, err := strconv.ParseUint(v, 10, 64)
76         if err != nil {
77                 panic(err)
78         }
79         fd := os.NewFile(uintptr(ptr), name)
80         if fd == nil {
81                 panic("can not parse fd: " + name)
82         }
83         return fd
84 }
85
86 func CmdName() string {
87         return path.Base(os.Args[0])
88 }
89
90 func main() {
91         version := flag.Bool("version", false, "print version")
92         warranty := flag.Bool("warranty", false, "print warranty information")
93
94         var symlinks *bool
95         cmdName := CmdName()
96         if cmdName == "goredo" {
97                 symlinks = flag.Bool("symlinks", false, "create necessary symlinks in current directory")
98         }
99
100         flag.Usage = func() { usage(os.Args[0]) }
101
102         BuildUUIDStr := os.Getenv(EnvBuildUUID)
103         IsTopRedo = BuildUUIDStr == ""
104
105         var args []string
106         if IsTopRedo {
107                 flag.Parse()
108                 args = flag.Args()
109         } else {
110                 args = os.Args[1:]
111         }
112
113         if *warranty {
114                 fmt.Println(Warranty)
115                 return
116         }
117         if *version {
118                 fmt.Println("goredo", Version, "built with", runtime.Version())
119                 return
120         }
121         if cmdName == CmdNameGoredo && *symlinks {
122                 rc := 0
123                 for _, cmdName := range []string{
124                         CmdNameRedo,
125                         CmdNameRedoAffects,
126                         CmdNameRedoAlways,
127                         CmdNameRedoCleanup,
128                         CmdNameRedoDep2Rec,
129                         CmdNameRedoDepFix,
130                         CmdNameRedoDot,
131                         CmdNameRedoIfchange,
132                         CmdNameRedoIfcreate,
133                         CmdNameRedoInode,
134                         CmdNameRedoLog,
135                         CmdNameRedoOOD,
136                         CmdNameRedoSources,
137                         CmdNameRedoStamp,
138                         CmdNameRedoTargets,
139                         CmdNameRedoWhichdo,
140                 } {
141                         fmt.Println(os.Args[0], "<-", cmdName)
142                         if err := os.Symlink(os.Args[0], cmdName); err != nil {
143                                 rc = 1
144                                 log.Print(err)
145                         }
146                 }
147                 os.Exit(rc)
148         }
149         log.SetFlags(log.Lshortfile)
150
151         UmaskCur = syscall.Umask(0)
152         syscall.Umask(UmaskCur)
153
154         var err error
155         Cwd, err = os.Getwd()
156         if err != nil {
157                 log.Fatal(err)
158         }
159
160         TopDir = os.Getenv(EnvTopDir)
161         if TopDir == "" {
162                 TopDir = "/"
163         } else {
164                 TopDir = mustAbs(TopDir)
165         }
166         DirPrefix = os.Getenv(EnvDirPrefix)
167         DepCwd = os.Getenv(EnvDepCwd)
168
169         if flagStderrKeep != nil && *flagStderrKeep {
170                 mustSetenv(EnvStderrKeep)
171         }
172         if flagStderrSilent != nil && *flagStderrSilent {
173                 mustSetenv(EnvStderrSilent)
174         }
175         if flagNoProgress != nil && *flagNoProgress {
176                 mustSetenv(EnvNoProgress)
177         }
178         if flagDebug != nil && *flagDebug {
179                 mustSetenv(EnvDebug)
180         }
181         if flagLogWait != nil && *flagLogWait {
182                 mustSetenv(EnvLogWait)
183         }
184         if flagLogLock != nil && *flagLogLock {
185                 mustSetenv(EnvLogLock)
186         }
187         if flagLogPid != nil && *flagLogPid {
188                 mustSetenv(EnvLogPid)
189         }
190         if flagLogJS != nil && *flagLogJS {
191                 mustSetenv(EnvLogJS)
192         }
193         StderrKeep = os.Getenv(EnvStderrKeep) == "1"
194         StderrSilent = os.Getenv(EnvStderrSilent) == "1"
195         NoProgress = os.Getenv(EnvNoProgress) == "1"
196         Debug = os.Getenv(EnvDebug) == "1"
197         LogWait = os.Getenv(EnvLogWait) == "1"
198         LogLock = os.Getenv(EnvLogLock) == "1"
199         LogJS = os.Getenv(EnvLogJS) == "1"
200         if Debug || os.Getenv(EnvLogPid) == "1" {
201                 MyPID = os.Getpid()
202         }
203         var traced bool
204         if flagTraceAll != nil && *flagTraceAll {
205                 mustSetenv(EnvTrace)
206         }
207         if os.Getenv(EnvTrace) == "1" {
208                 TracedAll = true
209                 traced = true
210         } else if flagTrace != nil {
211                 traced = *flagTrace
212         }
213         NoColor = os.Getenv(EnvNoColor) != ""
214         NoSync = os.Getenv(EnvNoSync) == "1"
215         StopIfMod = os.Getenv(EnvStopIfMod) == "1"
216         switch s := os.Getenv(EnvInodeTrust); s {
217         case "none":
218                 InodeTrust = InodeTrustNone
219         case "", "ctime":
220                 InodeTrust = InodeTrustCtime
221         case "mtime":
222                 InodeTrust = InodeTrustMtime
223         default:
224                 log.Fatalln("unknown", EnvInodeTrust, "value")
225         }
226
227         // Those are internal envs
228         FdOODTgts, err = os.CreateTemp("", "ood-tgts")
229         if err != nil {
230                 log.Fatal(err)
231         }
232         if err = os.Remove(FdOODTgts.Name()); err != nil {
233                 log.Fatal(err)
234         }
235         FdOODTgtsLock, err = os.CreateTemp("", "ood-tgts.lock")
236         if err != nil {
237                 log.Fatal(err)
238         }
239         if err = os.Remove(FdOODTgtsLock.Name()); err != nil {
240                 log.Fatal(err)
241         }
242
243         var fdLock *os.File
244         if v := os.Getenv(EnvOODTgtsFd); v != "" {
245                 fd := mustParseFd(v, EnvOODTgtsFd)
246                 fdLock = mustParseFd(v, EnvOODTgtsLockFd)
247                 defer fdLock.Close()
248                 flock := unix.Flock_t{
249                         Type:   unix.F_WRLCK,
250                         Whence: io.SeekStart,
251                 }
252                 if err = unix.FcntlFlock(fdLock.Fd(), unix.F_SETLKW, &flock); err != nil {
253                         log.Fatal(err)
254                 }
255                 if _, err = fd.Seek(0, io.SeekStart); err != nil {
256                         log.Fatal(err)
257                 }
258                 tgtsRaw, err := io.ReadAll(bufio.NewReader(fd))
259                 if err != nil {
260                         log.Fatal(err)
261                 }
262                 flock.Type = unix.F_UNLCK
263                 if err = unix.FcntlFlock(fdLock.Fd(), unix.F_SETLK, &flock); err != nil {
264                         log.Fatal(err)
265                 }
266                 OODTgts = make(map[string]struct{})
267                 for _, tgtRaw := range bytes.Split(tgtsRaw, []byte{0}) {
268                         t := string(tgtRaw)
269                         if t == "" {
270                                 continue
271                         }
272                         OODTgts[t] = struct{}{}
273                         tracef(CDebug, "ood: known to be: %s", t)
274                 }
275         }
276
277         StderrPrefix = os.Getenv(EnvStderrPrefix)
278         if v := os.Getenv(EnvLevel); v != "" {
279                 Level, err = strconv.Atoi(v)
280                 if err != nil {
281                         panic(err)
282                 }
283                 if Level < 0 {
284                         panic("negative " + EnvLevel)
285                 }
286         }
287
288         var fdDep *os.File
289         if v := os.Getenv(EnvDepFd); v != "" {
290                 fdDep = mustParseFd(v, EnvDepFd)
291         }
292
293         tgts := make([]*Tgt, 0, len(args))
294         for _, arg := range args {
295                 tgts = append(tgts, NewTgt(arg))
296         }
297         tgtsWasEmpty := len(tgts) == 0
298         if BuildUUIDStr == "" {
299                 BuildUUID = uuid.New()
300                 if tgtsWasEmpty {
301                         tgts = append(tgts, NewTgt("all"))
302                 }
303                 tracef(CDebug, "inode-trust: %s", InodeTrust)
304         } else {
305                 BuildUUID, err = uuid.Parse(BuildUUIDStr)
306                 if err != nil {
307                         log.Fatal(err)
308                 }
309         }
310
311         if cmdName == CmdNameRedo {
312                 statusInit()
313         }
314
315         killed := make(chan os.Signal, 1)
316         signal.Notify(killed, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
317         go func() {
318                 <-killed
319                 tracef(CDebug, "[%s] killed", BuildUUID)
320                 jsReleaseAll()
321                 RunningProcsM.Lock()
322                 for pid, proc := range RunningProcs {
323                         tracef(CDebug, "[%s] killing child %d", BuildUUID, pid)
324                         _ = proc.Signal(syscall.SIGTERM)
325                 }
326                 os.Exit(1)
327         }()
328
329         ok := true
330         err = nil
331         tracef(CDebug, "[%s] run: %s %s cwd:%s dirprefix:%s",
332                 BuildUUID, cmdName, tgts, Cwd, DirPrefix)
333
334         switch cmdName {
335         case CmdNameRedo:
336                 for _, tgt := range tgts {
337                         ok, err = ifchange([]*Tgt{tgt}, true, traced)
338                         if err != nil || !ok {
339                                 break
340                         }
341                 }
342         case CmdNameRedoIfchange:
343                 ok, err = ifchange(tgts, *flagForcedIfchange, traced)
344                 if err == nil {
345                         err = depsWrite(fdDep, tgts)
346                 }
347         case CmdNameRedoIfcreate:
348                 if fdDep == nil {
349                         log.Fatalln("no", EnvDepFd)
350                 }
351                 fdDepW := bufio.NewWriter(fdDep)
352                 for _, tgt := range tgts {
353                         err = ifcreate(fdDepW, fdDep.Name(), tgt.RelTo(path.Join(Cwd, DirPrefix)))
354                         if err != nil {
355                                 break
356                         }
357                 }
358                 err = fdDepW.Flush()
359         case CmdNameRedoAlways:
360                 if fdDep == nil {
361                         log.Fatalln("no", EnvDepFd)
362                 }
363                 err = always(fdDep, fdDep.Name())
364         case CmdNameRedoCleanup:
365                 for _, what := range tgts {
366                         err = cleanupWalker(Cwd, path.Base(what.a))
367                         if err != nil {
368                                 break
369                         }
370                 }
371         case CmdNameRedoDot:
372                 err = dotPrint(tgts)
373         case CmdNameRedoStamp:
374                 if fdDep == nil {
375                         log.Fatalln("no", EnvDepFd)
376                 }
377                 var hsh Hash
378                 hsh, err = fileHash(os.Stdin)
379                 if err != nil {
380                         break
381                 }
382                 err = stamp(fdDep, fdDep.Name(), hsh)
383         case CmdNameRedoLog:
384                 if len(tgts) != 1 {
385                         log.Fatal("single target expected")
386                 }
387                 err = showBuildLog(tgts[0], nil, 0)
388         case CmdNameRedoWhichdo:
389                 if len(tgts) != 1 {
390                         log.Fatal("single target expected")
391                 }
392                 var dos []string
393                 dos, err = whichdo(tgts[0])
394                 if err != nil {
395                         break
396                 }
397                 for _, do := range dos {
398                         fmt.Println(do)
399                 }
400         case CmdNameRedoTargets:
401                 raws := make([]string, 0, len(tgts))
402                 for _, tgt := range tgts {
403                         raws = append(raws, tgt.rel)
404                 }
405                 if tgtsWasEmpty {
406                         raws = []string{Cwd}
407                 }
408                 raws, err = targetsWalker(raws)
409                 if err != nil {
410                         err = ErrLine(err)
411                         break
412                 }
413                 sort.Strings(raws)
414                 for _, tgt := range raws {
415                         fmt.Println(tgt)
416                 }
417         case CmdNameRedoAffects:
418                 if tgtsWasEmpty {
419                         log.Fatal("no targets specified")
420                 }
421                 var res []string
422                 {
423                         var tgtsKnown []string
424                         tgtsKnown, err = targetsWalker([]string{Cwd})
425                         if err != nil {
426                                 err = ErrLine(err)
427                                 break
428                         }
429                         deps := make(map[string]map[string]*Tgt)
430                         for _, tgt := range tgtsKnown {
431                                 collectDeps(NewTgt(tgt), 0, deps, true)
432                         }
433                         seen := make(map[string]*Tgt)
434                         for _, tgt := range tgts {
435                                 collectWholeDeps(deps[tgt.rel], deps, seen)
436                         }
437                         res = make([]string, 0, len(seen))
438                         for _, dep := range seen {
439                                 res = append(res, dep.rel)
440                         }
441                 }
442                 sort.Strings(res)
443                 for _, dep := range res {
444                         fmt.Println(dep)
445                 }
446         case CmdNameRedoOOD:
447                 raws := make([]string, 0, len(tgts))
448                 for _, tgt := range tgts {
449                         raws = append(raws, tgt.rel)
450                 }
451                 if tgtsWasEmpty {
452                         raws, err = targetsWalker([]string{Cwd})
453                         if err != nil {
454                                 break
455                         }
456                 }
457                 sort.Strings(raws)
458                 var ood bool
459                 for _, tgt := range raws {
460                         ood, err = isOOD(NewTgt(tgt), 0, nil)
461                         if err != nil {
462                                 err = ErrLine(err)
463                                 break
464                         }
465                         if ood {
466                                 fmt.Println(tgt)
467                         }
468                 }
469         case CmdNameRedoSources:
470                 srcs := make(map[string]*Tgt)
471                 {
472                         raws := make([]string, 0, len(tgts))
473                         for _, tgt := range tgts {
474                                 raws = append(raws, tgt.rel)
475                         }
476                         if tgtsWasEmpty {
477                                 raws, err = targetsWalker([]string{Cwd})
478                                 if err != nil {
479                                         err = ErrLine(err)
480                                         break
481                                 }
482                         }
483                         sort.Strings(raws)
484                         tgts = tgts[:0]
485                         for _, raw := range raws {
486                                 tgts = append(tgts, NewTgt(raw))
487                         }
488                         seen := make(map[string]struct{})
489                         seenDeps := make(map[string]struct{})
490                         err = ErrLine(sourcesWalker(tgts, seen, seenDeps, srcs))
491                 }
492                 if err != nil {
493                         break
494                 }
495                 res := make([]string, 0, len(srcs))
496                 for _, tgt := range srcs {
497                         res = append(res, tgt.rel)
498                 }
499                 srcs = nil
500                 sort.Strings(res)
501                 for _, src := range res {
502                         fmt.Println(src)
503                 }
504         case CmdNameRedoDepFix:
505                 IfchangeCache = nil
506                 DepFixHashCache = make(map[string]Hash)
507                 err = depFix(Cwd)
508         case CmdNameRedoInode:
509                 var inode *Inode
510                 for _, tgt := range tgts {
511                         inode, err = inodeFromFileByPath(tgt.a)
512                         if err != nil {
513                                 err = ErrLine(err)
514                                 break
515                         }
516                         err = recfileWrite(os.Stdout, append(
517                                 []recfile.Field{{Name: "Target", Value: tgt.String()}},
518                                 inode.RecfileFields()...)...)
519                         if err != nil {
520                                 err = ErrLine(err)
521                                 break
522                         }
523                 }
524         case CmdNameRedoDep2Rec:
525                 var data []byte
526                 data, err = os.ReadFile(tgts[0].a)
527                 if err != nil {
528                         break
529                 }
530                 var build uuid.UUID
531                 build, data, err = depHeadParse(data)
532                 if err != nil {
533                         break
534                 }
535                 w := bufio.NewWriter(os.Stdout)
536                 err = recfileWrite(w, []recfile.Field{
537                         {Name: "Build", Value: build.String()},
538                 }...)
539                 if err != nil {
540                         break
541                 }
542                 var typ byte
543                 var chunk []byte
544                 var inode Inode
545                 for len(data) > 0 {
546                         typ, chunk, data, _ = chunkRead(data)
547                         switch typ {
548                         case DepTypeAlways:
549                                 err = recfileWrite(w, []recfile.Field{
550                                         {Name: "Type", Value: "always"},
551                                 }...)
552                         case DepTypeStamp:
553                                 err = recfileWrite(w, []recfile.Field{
554                                         {Name: "Type", Value: "stamp"},
555                                         {Name: "Hash", Value: hex.EncodeToString(chunk)},
556                                 }...)
557                         case DepTypeIfcreate:
558                                 err = recfileWrite(w, []recfile.Field{
559                                         {Name: "Type", Value: "ifcreate"},
560                                         {Name: "Target", Value: string(chunk)},
561                                 }...)
562                         case DepTypeIfchange:
563                                 name := string(chunk[InodeLen+HashLen:])
564                                 meta := chunk[:InodeLen+HashLen]
565                                 fields := []recfile.Field{
566                                         {Name: "Type", Value: "ifchange"},
567                                         {Name: "Target", Value: name},
568                                 }
569                                 fields = append(fields, recfile.Field{
570                                         Name: "Hash", Value: Hash(meta[InodeLen:]).String(),
571                                 })
572                                 inode = Inode(meta[:][:InodeLen])
573                                 fields = append(fields, inode.RecfileFields()...)
574                                 err = recfileWrite(w, fields...)
575                         case DepTypeIfchangeNonex:
576                                 err = recfileWrite(w, []recfile.Field{
577                                         {Name: "Type", Value: "ifchange"},
578                                         {Name: "Target", Value: string(chunk)},
579                                 }...)
580                         }
581                         if err != nil {
582                                 break
583                         }
584                 }
585                 err = w.Flush()
586         default:
587                 log.Fatalln("unknown command", cmdName)
588         }
589         if err != nil {
590                 log.Print(err)
591         }
592         rc := 0
593         if !ok || err != nil {
594                 rc = 1
595         }
596         tracef(CDebug, "[%s] finished: %s %s", BuildUUID, cmdName, tgts)
597         os.Exit(rc)
598 }