]> Cypherpunks.ru repositories - goredo.git/blob - main.go
Unify dep*Read/Write name
[goredo.git] / main.go
1 /*
2 goredo -- djb's redo implementation on pure Go
3 Copyright (C) 2020-2021 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         "crypto/rand"
24         "errors"
25         "flag"
26         "fmt"
27         "io"
28         "io/ioutil"
29         "log"
30         "os"
31         "os/signal"
32         "path"
33         "path/filepath"
34         "runtime"
35         "sort"
36         "strconv"
37         "syscall"
38
39         "go.cypherpunks.ru/recfile"
40         "golang.org/x/sys/unix"
41 )
42
43 const (
44         CmdNameGoredo       = "goredo"
45         CmdNameRedo         = "redo"
46         CmdNameRedoAffects  = "redo-affects"
47         CmdNameRedoAlways   = "redo-always"
48         CmdNameRedoCleanup  = "redo-cleanup"
49         CmdNameRedoDot      = "redo-dot"
50         CmdNameRedoIfchange = "redo-ifchange"
51         CmdNameRedoIfcreate = "redo-ifcreate"
52         CmdNameRedoLog      = "redo-log"
53         CmdNameRedoOOD      = "redo-ood"
54         CmdNameRedoSources  = "redo-sources"
55         CmdNameRedoStamp    = "redo-stamp"
56         CmdNameRedoTargets  = "redo-targets"
57         CmdNameRedoWhichdo  = "redo-whichdo"
58 )
59
60 var (
61         Cwd       string
62         BuildUUID string
63         IsTopRedo bool // is it the top redo instance
64 )
65
66 func mustSetenv(key string) {
67         if err := os.Setenv(key, "1"); err != nil {
68                 panic(err)
69         }
70 }
71
72 func mustParseFd(v, name string) *os.File {
73         ptr, err := strconv.ParseUint(v, 10, 64)
74         if err != nil {
75                 panic(err)
76         }
77         fd := os.NewFile(uintptr(ptr), name)
78         if fd == nil {
79                 panic("can not parse fd: " + name)
80         }
81         return fd
82 }
83
84 func CmdName() string {
85         return path.Base(os.Args[0])
86 }
87
88 func main() {
89         version := flag.Bool("version", false, "print version")
90         warranty := flag.Bool("warranty", false, "print warranty information")
91
92         var symlinks *bool
93         cmdName := CmdName()
94         if cmdName == "goredo" {
95                 symlinks = flag.Bool("symlinks", false, "create necessary symlinks in current directory")
96         }
97
98         flag.Usage = func() { usage(os.Args[0]) }
99         flag.Parse()
100         if *warranty {
101                 fmt.Println(Warranty)
102                 return
103         }
104         if *version {
105                 fmt.Println("goredo", Version, "built with", runtime.Version())
106                 return
107         }
108         if cmdName == CmdNameGoredo && *symlinks {
109                 rc := 0
110                 for _, cmdName := range []string{
111                         CmdNameRedo,
112                         CmdNameRedoAffects,
113                         CmdNameRedoAlways,
114                         CmdNameRedoCleanup,
115                         CmdNameRedoDot,
116                         CmdNameRedoIfchange,
117                         CmdNameRedoIfcreate,
118                         CmdNameRedoLog,
119                         CmdNameRedoOOD,
120                         CmdNameRedoSources,
121                         CmdNameRedoStamp,
122                         CmdNameRedoTargets,
123                         CmdNameRedoWhichdo,
124                 } {
125                         fmt.Println(os.Args[0], "<-", cmdName)
126                         if err := os.Symlink(os.Args[0], cmdName); err != nil {
127                                 rc = 1
128                                 log.Println(err)
129                         }
130                 }
131                 os.Exit(rc)
132         }
133         log.SetFlags(log.Lshortfile)
134
135         var err error
136         Cwd, err = os.Getwd()
137         if err != nil {
138                 log.Fatalln(err)
139         }
140
141         NoColor = os.Getenv(EnvNoColor) != ""
142         NoSync = os.Getenv(EnvNoSync) == "1"
143         InodeTrust = os.Getenv(EnvInodeNoTrust) == ""
144
145         TopDir = os.Getenv(EnvTopDir)
146         if TopDir == "" {
147                 TopDir = "/"
148         } else {
149                 TopDir, err = filepath.Abs(TopDir)
150                 if err != nil {
151                         panic(err)
152                 }
153         }
154         DirPrefix = os.Getenv(EnvDirPrefix)
155         DepCwd = os.Getenv(EnvDepCwd)
156
157         if flagStderrKeep != nil && *flagStderrKeep {
158                 mustSetenv(EnvStderrKeep)
159         }
160         if flagStderrSilent != nil && *flagStderrSilent {
161                 mustSetenv(EnvStderrSilent)
162         }
163         if flagNoProgress != nil && *flagNoProgress {
164                 mustSetenv(EnvNoProgress)
165         }
166         if flagDebug != nil && *flagDebug {
167                 mustSetenv(EnvDebug)
168         }
169         if flagLogWait != nil && *flagLogWait {
170                 mustSetenv(EnvLogWait)
171         }
172         if flagLogLock != nil && *flagLogLock {
173                 mustSetenv(EnvLogLock)
174         }
175         if flagLogPid != nil && *flagLogPid {
176                 mustSetenv(EnvLogPid)
177         }
178         if flagLogJS != nil && *flagLogJS {
179                 mustSetenv(EnvLogJS)
180         }
181         StderrKeep = os.Getenv(EnvStderrKeep) == "1"
182         StderrSilent = os.Getenv(EnvStderrSilent) == "1"
183         NoProgress = os.Getenv(EnvNoProgress) == "1"
184         Debug = os.Getenv(EnvDebug) == "1"
185         LogWait = os.Getenv(EnvLogWait) == "1"
186         LogLock = os.Getenv(EnvLogLock) == "1"
187         LogJS = os.Getenv(EnvLogJS) == "1"
188         if Debug || os.Getenv(EnvLogPid) == "1" {
189                 MyPid = os.Getpid()
190         }
191         var traced bool
192         if flagTraceAll != nil && *flagTraceAll {
193                 mustSetenv(EnvTrace)
194         }
195         if os.Getenv(EnvTrace) == "1" {
196                 TracedAll = true
197                 traced = true
198         } else if flagTrace != nil {
199                 traced = *flagTrace
200         }
201
202         // Those are internal envs
203         FdOODTgts, err = ioutil.TempFile("", "ood-tgts")
204         if err != nil {
205                 log.Fatalln(err)
206         }
207         if err = os.Remove(FdOODTgts.Name()); err != nil {
208                 log.Fatalln(err)
209         }
210         FdOODTgtsLock, err = ioutil.TempFile("", "ood-tgts.lock")
211         if err != nil {
212                 log.Fatalln(err)
213         }
214         if err = os.Remove(FdOODTgtsLock.Name()); err != nil {
215                 log.Fatalln(err)
216         }
217
218         if v := os.Getenv(EnvOODTgtsFd); v != "" {
219                 fd := mustParseFd(v, EnvOODTgtsFd)
220                 fdLock := mustParseFd(v, EnvOODTgtsLockFd)
221                 if err = unix.Flock(int(fdLock.Fd()), unix.LOCK_EX); err != nil {
222                         log.Fatalln(err)
223                 }
224                 if _, err = fd.Seek(0, io.SeekStart); err != nil {
225                         log.Fatalln(err)
226                 }
227                 tgtsRaw, err := ioutil.ReadAll(bufio.NewReader(fd))
228                 if err != nil {
229                         log.Fatalln(err)
230                 }
231                 if err = unix.Flock(int(fdLock.Fd()), unix.LOCK_UN); err != nil {
232                         log.Fatalln(err)
233                 }
234                 OODTgts = map[string]struct{}{}
235                 for _, tgtRaw := range bytes.Split(tgtsRaw, []byte{0}) {
236                         t := string(tgtRaw)
237                         if t == "" {
238                                 continue
239                         }
240                         OODTgts[t] = struct{}{}
241                         tracef(CDebug, "ood: known to be: %s", t)
242                 }
243         }
244
245         StderrPrefix = os.Getenv(EnvStderrPrefix)
246         if v := os.Getenv(EnvLevel); v != "" {
247                 Level, err = strconv.Atoi(v)
248                 if err != nil {
249                         panic(err)
250                 }
251                 if Level < 0 {
252                         panic("negative " + EnvLevel)
253                 }
254         }
255
256         var fdDep *os.File
257         if v := os.Getenv(EnvDepFd); v != "" {
258                 fdDep = mustParseFd(v, EnvDepFd)
259         }
260
261         tgts := flag.Args()
262         BuildUUID = os.Getenv(EnvBuildUUID)
263         tgtsWasEmpty := len(tgts) == 0
264         if BuildUUID == "" {
265                 IsTopRedo = true
266                 raw := new([16]byte)
267                 if _, err = io.ReadFull(rand.Reader, raw[:]); err != nil {
268                         log.Fatalln(err)
269                 }
270                 raw[6] = (raw[6] & 0x0F) | uint8(4<<4) // version 4
271                 BuildUUID = fmt.Sprintf(
272                         "%x-%x-%x-%x-%x",
273                         raw[0:4], raw[4:6], raw[6:8], raw[8:10], raw[10:],
274                 )
275                 if tgtsWasEmpty {
276                         tgts = []string{"all"}
277                 }
278         }
279
280         if cmdName == CmdNameRedo || cmdName == CmdNameRedoIfchange {
281                 statusInit()
282         }
283
284         for i, tgt := range tgts {
285                 if path.IsAbs(tgt) {
286                         tgts[i] = cwdMustRel(tgt)
287                 }
288         }
289
290         killed := make(chan os.Signal, 1)
291         signal.Notify(killed, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
292         go func() {
293                 <-killed
294                 tracef(CDebug, "[%s] killed", BuildUUID)
295                 jsReleaseAll()
296                 RunningProcsM.Lock()
297                 for pid, proc := range RunningProcs {
298                         tracef(CDebug, "[%s] killing child %d", BuildUUID, pid)
299                         _ = proc.Signal(syscall.SIGTERM)
300                 }
301                 os.Exit(1)
302         }()
303
304         ok := true
305         err = nil
306         tracef(
307                 CDebug, "[%s] run: %s %s cwd:%s dirprefix:%s",
308                 BuildUUID, cmdName, tgts, Cwd, DirPrefix,
309         )
310
311 CmdSwitch:
312         switch cmdName {
313         case CmdNameRedo:
314                 for _, tgt := range tgts {
315                         ok, err = ifchange([]string{tgt}, true, traced)
316                         if err != nil || !ok {
317                                 break
318                         }
319                 }
320         case CmdNameRedoIfchange:
321                 ok, err = ifchange(tgts, false, traced)
322                 if err == nil {
323                         err = depsWrite(fdDep, tgts)
324                 }
325         case CmdNameRedoIfcreate:
326                 if fdDep == nil {
327                         log.Fatalln("no", EnvDepFd)
328                 }
329                 for _, tgt := range tgts {
330                         tgtRel, err := filepath.Rel(
331                                 filepath.Join(Cwd, DirPrefix),
332                                 filepath.Join(Cwd, tgt),
333                         )
334                         if err != nil {
335                                 break
336                         }
337                         err = ifcreate(fdDep, tgtRel)
338                         if err != nil {
339                                 break
340                         }
341                 }
342         case CmdNameRedoAlways:
343                 if fdDep == nil {
344                         log.Fatalln("no", EnvDepFd)
345                 }
346                 err = always(fdDep)
347         case CmdNameRedoCleanup:
348                 for _, what := range tgts {
349                         err = cleanupWalker(Cwd, what)
350                         if err != nil {
351                                 break
352                         }
353                 }
354         case CmdNameRedoDot:
355                 err = dotPrint(tgts)
356         case CmdNameRedoStamp:
357                 if fdDep == nil {
358                         log.Fatalln("no", EnvDepFd)
359                 }
360                 err = stamp(fdDep, os.Stdin)
361         case CmdNameRedoLog:
362                 if len(tgts) != 1 {
363                         log.Fatalln("single target expected")
364                 }
365                 d, t := cwdAndTgt(tgts[0])
366                 err = showBuildLog(d, t, nil, 0)
367         case CmdNameRedoWhichdo:
368                 if len(tgts) != 1 {
369                         log.Fatalln("single target expected")
370                 }
371                 var fdTmp *os.File
372                 fdTmp, err = ioutil.TempFile("", "whichdo")
373                 if err != nil {
374                         break
375                 }
376                 if err = os.Remove(fdTmp.Name()); err != nil {
377                         break
378                 }
379                 cwd, tgt := cwdAndTgt(tgts[0])
380                 doFile, upLevels, err := findDo(fdTmp, cwd, tgt)
381                 if err != nil {
382                         break
383                 }
384                 _, err = fdTmp.Seek(0, io.SeekStart)
385                 if err != nil {
386                         break
387                 }
388                 r := recfile.NewReader(fdTmp)
389                 for {
390                         m, err := r.NextMap()
391                         if err != nil {
392                                 if errors.Is(err, io.EOF) {
393                                         break
394                                 }
395                                 break CmdSwitch
396                         }
397                         fmt.Println(cwdMustRel(cwd, m["Target"]))
398                 }
399                 if doFile == "" {
400                         ok = false
401                 } else {
402                         p := make([]string, 0, upLevels+2)
403                         p = append(p, cwd)
404                         for i := 0; i < upLevels; i++ {
405                                 p = append(p, "..")
406                         }
407                         p = append(p, doFile)
408                         rel, err := filepath.Rel(Cwd, path.Join(p...))
409                         if err != nil {
410                                 panic(err)
411                         }
412                         fmt.Println(rel)
413                 }
414         case CmdNameRedoTargets:
415                 if tgtsWasEmpty {
416                         tgts = []string{Cwd}
417                 }
418                 tgts, err = targetsWalker(tgts)
419                 if err != nil {
420                         break
421                 }
422                 sort.Strings(tgts)
423                 for _, tgt := range tgts {
424                         fmt.Println(tgt)
425                 }
426         case CmdNameRedoAffects:
427                 if tgtsWasEmpty {
428                         log.Fatalln("no targets specified")
429                 }
430                 var tgtsKnown []string
431                 tgtsKnown, err = targetsWalker([]string{Cwd})
432                 if err != nil {
433                         break
434                 }
435                 deps := map[string]map[string]struct{}{}
436                 for _, tgt := range tgtsKnown {
437                         collectDeps(Cwd, tgt, 0, deps, true)
438                 }
439                 seen := map[string]struct{}{}
440                 for _, tgt := range tgts {
441                         collectWholeDeps(deps[tgt], deps, seen)
442                 }
443                 tgts := make([]string, 0, len(seen))
444                 for dep := range seen {
445                         tgts = append(tgts, dep)
446                 }
447                 sort.Strings(tgts)
448                 for _, dep := range tgts {
449                         fmt.Println(dep)
450                 }
451         case CmdNameRedoOOD:
452                 if tgtsWasEmpty {
453                         tgts, err = targetsWalker([]string{Cwd})
454                         if err != nil {
455                                 break
456                         }
457                 }
458                 sort.Strings(tgts)
459                 var ood bool
460                 for _, tgt := range tgts {
461                         ood, err = isOOD(Cwd, tgt, 0, nil)
462                         if err != nil {
463                                 break
464                         }
465                         if ood {
466                                 fmt.Println(tgt)
467                         }
468                 }
469         case CmdNameRedoSources:
470                 if tgtsWasEmpty {
471                         tgts, err = targetsWalker([]string{Cwd})
472                         if err != nil {
473                                 break
474                         }
475                 }
476                 sort.Strings(tgts)
477                 var srcs []string
478                 srcs, err = sourcesWalker(tgts)
479                 sort.Strings(srcs)
480                 for _, src := range srcs {
481                         fmt.Println(src)
482                 }
483         default:
484                 log.Fatalln("unknown command", cmdName)
485         }
486         if err != nil {
487                 log.Println(err)
488         }
489         rc := 0
490         if !ok || err != nil {
491                 rc = 1
492         }
493         tracef(CDebug, "[%s] finished: %s %s", BuildUUID, cmdName, tgts)
494         os.Exit(rc)
495 }