]> Cypherpunks.ru repositories - goredo.git/blob - main.go
Separate usage help for each command
[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         "flag"
25         "fmt"
26         "io"
27         "io/ioutil"
28         "log"
29         "os"
30         "path"
31         "path/filepath"
32         "runtime"
33         "strconv"
34
35         "go.cypherpunks.ru/recfile"
36         "golang.org/x/sys/unix"
37 )
38
39 var (
40         Cwd       string
41         BuildUUID string
42 )
43
44 func mustSetenv(key, value string) {
45         if err := os.Setenv(key, value); err != nil {
46                 panic(err)
47         }
48 }
49
50 func mustParseFd(v, name string) *os.File {
51         ptr, err := strconv.ParseUint(v, 10, 64)
52         if err != nil {
53                 panic(err)
54         }
55         fd := os.NewFile(uintptr(ptr), name)
56         if fd == nil {
57                 panic("can not parse fd: " + name)
58         }
59         return fd
60 }
61
62 func main() {
63         version := flag.Bool("version", false, "print version")
64         warranty := flag.Bool("warranty", false, "print warranty information")
65         symlinks := flag.Bool("symlinks", false, "create necessary symlinks in current directory")
66
67         flag.Usage = func() { usage(os.Args[0]) }
68         flag.Parse()
69         if *warranty {
70                 fmt.Println(Warranty)
71                 return
72         }
73         if *version {
74                 fmt.Println("goredo", Version, "built with", runtime.Version())
75                 return
76         }
77         if *symlinks {
78                 rc := 0
79                 for _, cmdName := range []string{
80                         "redo",
81                         "redo-always",
82                         "redo-cleanup",
83                         "redo-dot",
84                         "redo-ifchange",
85                         "redo-ifcreate",
86                         "redo-log",
87                         "redo-stamp",
88                         "redo-whichdo",
89                 } {
90                         fmt.Println(os.Args[0], "<-", cmdName)
91                         if err := os.Symlink(os.Args[0], cmdName); err != nil {
92                                 rc = 1
93                                 log.Println(err)
94                         }
95                 }
96                 os.Exit(rc)
97         }
98         log.SetFlags(0)
99
100         var err error
101         Cwd, err = os.Getwd()
102         if err != nil {
103                 log.Fatalln(err)
104         }
105
106         NoColor = os.Getenv(EnvNoColor) != ""
107         NoSync = os.Getenv(EnvNoSync) == "1"
108         InodeTrust = os.Getenv(EnvInodeNoTrust) == ""
109
110         TopDir = os.Getenv(EnvTopDir)
111         if TopDir == "" {
112                 TopDir = "/"
113         } else {
114                 TopDir, err = filepath.Abs(TopDir)
115                 if err != nil {
116                         panic(err)
117                 }
118         }
119         DirPrefix = os.Getenv(EnvDirPrefix)
120
121         if *flagStderrKeep {
122                 mustSetenv(EnvStderrKeep, "1")
123         }
124         if *flagStderrSilent {
125                 mustSetenv(EnvStderrSilent, "1")
126         }
127         if *flagNoProgress {
128                 mustSetenv(EnvNoProgress, "1")
129         }
130         if *flagDebug {
131                 mustSetenv(EnvDebug, "1")
132         }
133         if *flagLogWait {
134                 mustSetenv(EnvLogWait, "1")
135         }
136         if *flagLogLock {
137                 mustSetenv(EnvLogLock, "1")
138         }
139         if *flagLogPid {
140                 mustSetenv(EnvLogPid, "1")
141         }
142         if *flagLogJS {
143                 mustSetenv(EnvLogJS, "1")
144         }
145         StderrKeep = os.Getenv(EnvStderrKeep) == "1"
146         StderrSilent = os.Getenv(EnvStderrSilent) == "1"
147         NoProgress = os.Getenv(EnvNoProgress) == "1"
148         Debug = os.Getenv(EnvDebug) == "1"
149         LogWait = os.Getenv(EnvLogWait) == "1"
150         LogLock = os.Getenv(EnvLogLock) == "1"
151         LogJS = os.Getenv(EnvLogJS) == "1"
152         if Debug || os.Getenv(EnvLogPid) == "1" {
153                 MyPid = os.Getpid()
154         }
155         var traced bool
156         if *flagTraceAll {
157                 mustSetenv(EnvTrace, "1")
158         }
159         if *flagTrace {
160                 traced = true
161         } else {
162                 traced = os.Getenv(EnvTrace) == "1"
163         }
164
165         // Those are internal envs
166         FdOODTgts, err = ioutil.TempFile("", "ood-tgts")
167         if err != nil {
168                 panic(err)
169         }
170         if err = os.Remove(FdOODTgts.Name()); err != nil {
171                 panic(err)
172         }
173         FdOODTgtsLock, err = ioutil.TempFile("", "ood-tgts.lock")
174         if err != nil {
175                 panic(err)
176         }
177         if err = os.Remove(FdOODTgtsLock.Name()); err != nil {
178                 panic(err)
179         }
180
181         if v := os.Getenv(EnvOODTgtsFd); v != "" {
182                 fd := mustParseFd(v, EnvOODTgtsFd)
183                 fdLock := mustParseFd(v, EnvOODTgtsLockFd)
184                 if err = unix.Flock(int(fdLock.Fd()), unix.LOCK_EX); err != nil {
185                         panic(err)
186                 }
187                 if _, err = fd.Seek(0, os.SEEK_SET); err != nil {
188                         panic(err)
189                 }
190                 tgtsRaw, err := ioutil.ReadAll(bufio.NewReader(fd))
191                 if err != nil {
192                         panic(err)
193                 }
194                 unix.Flock(int(fdLock.Fd()), unix.LOCK_UN)
195                 OODTgts = make(map[string]struct{})
196                 for _, tgtRaw := range bytes.Split(tgtsRaw, []byte{0}) {
197                         t := string(tgtRaw)
198                         if t == "" {
199                                 continue
200                         }
201                         OODTgts[t] = struct{}{}
202                         trace(CDebug, "ood: known to be: %s", t)
203                 }
204         }
205
206         StderrPrefix = os.Getenv(EnvStderrPrefix)
207         if v := os.Getenv(EnvLevel); v != "" {
208                 Level, err = strconv.Atoi(v)
209                 if err != nil {
210                         panic(err)
211                 }
212                 if Level < 0 {
213                         panic("negative " + EnvLevel)
214                 }
215         }
216
217         var fdDep *os.File
218         if v := os.Getenv(EnvDepFd); v != "" {
219                 fdDep = mustParseFd(v, EnvDepFd)
220         }
221
222         tgts := flag.Args()
223         BuildUUID = os.Getenv(EnvBuildUUID)
224         if BuildUUID == "" {
225                 raw := new([16]byte)
226                 if _, err = io.ReadFull(rand.Reader, raw[:]); err != nil {
227                         log.Fatalln(err)
228                 }
229                 raw[6] = (raw[6] & 0x0F) | uint8(4<<4) // version 4
230                 BuildUUID = fmt.Sprintf(
231                         "%x-%x-%x-%x-%x",
232                         raw[0:4], raw[4:6], raw[6:8], raw[8:10], raw[10:],
233                 )
234                 if len(tgts) == 0 {
235                         tgts = []string{"all"}
236                 }
237         }
238
239         statusInit()
240
241         for i, tgt := range tgts {
242                 if path.IsAbs(tgt) {
243                         tgts[i] = cwdMustRel(tgt)
244                 }
245         }
246
247         ok := true
248         err = nil
249         cmdName := path.Base(os.Args[0])
250         trace(
251                 CDebug, "[%s] run: %s %s cwd:%s dirprefix:%s",
252                 BuildUUID, cmdName, tgts, Cwd, DirPrefix,
253         )
254
255 CmdSwitch:
256         switch cmdName {
257         case "redo":
258                 for _, tgt := range tgts {
259                         ok, err = ifchange([]string{tgt}, true, traced)
260                         if err != nil || !ok {
261                                 break
262                         }
263                 }
264         case "redo-ifchange":
265                 ok, err = ifchange(tgts, false, traced)
266                 if err == nil {
267                         err = writeDeps(fdDep, tgts)
268                 }
269         case "redo-ifcreate":
270                 if fdDep == nil {
271                         log.Fatalln("no", EnvDepFd)
272                 }
273                 for _, tgt := range tgts {
274                         err = ifcreate(fdDep, tgt)
275                         if err != nil {
276                                 break
277                         }
278                 }
279         case "redo-always":
280                 if fdDep == nil {
281                         log.Fatalln("no", EnvDepFd)
282                 }
283                 err = always(fdDep)
284         case "redo-cleanup":
285                 for _, what := range tgts {
286                         err = cleanupWalker(Cwd, what)
287                         if err != nil {
288                                 break
289                         }
290                 }
291         case "redo-dot":
292                 err = dotPrint(tgts)
293         case "redo-stamp":
294                 if fdDep == nil {
295                         log.Fatalln("no", EnvDepFd)
296                 }
297                 err = stamp(fdDep, os.Stdin)
298         case "redo-log":
299                 if len(tgts) != 1 {
300                         log.Fatalln("single target expected")
301                 }
302                 d, t := cwdAndTgt(tgts[0])
303                 var fd *os.File
304                 fd, err = os.Open(path.Join(d, RedoDir, t+LogSuffix))
305                 if err != nil {
306                         break
307                 }
308                 _, err = io.Copy(os.Stdout, fd)
309         case "redo-whichdo":
310                 if len(tgts) != 1 {
311                         log.Fatalln("single target expected")
312                 }
313                 var fdTmp *os.File
314                 fdTmp, err = ioutil.TempFile("", "whichdo")
315                 if err != nil {
316                         break
317                 }
318                 if err = os.Remove(fdTmp.Name()); err != nil {
319                         break
320                 }
321                 cwd, tgt := cwdAndTgt(tgts[0])
322                 doFile, upLevels, err := findDo(fdTmp, cwd, tgt)
323                 if err != nil {
324                         break
325                 }
326                 _, err = fdTmp.Seek(0, os.SEEK_SET)
327                 if err != nil {
328                         break
329                 }
330                 r := recfile.NewReader(fdTmp)
331                 for {
332                         m, err := r.NextMap()
333                         if err != nil {
334                                 if err == io.EOF {
335                                         break
336                                 }
337                                 break CmdSwitch
338                         }
339                         fmt.Println(cwdMustRel(cwd, m["Target"]))
340                 }
341                 if doFile == "" {
342                         ok = false
343                 } else {
344                         p := make([]string, 0, upLevels+2)
345                         p = append(p, cwd)
346                         for i := 0; i < upLevels; i++ {
347                                 p = append(p, "..")
348                         }
349                         p = append(p, doFile)
350                         rel, err := filepath.Rel(Cwd, path.Join(p...))
351                         if err != nil {
352                                 panic(err)
353                         }
354                         fmt.Println(rel)
355                 }
356         default:
357                 log.Fatalln("unknown command", cmdName)
358         }
359         if err != nil {
360                 log.Println(err)
361         }
362         rc := 0
363         if !ok || err != nil {
364                 rc = 1
365         }
366         trace(CDebug, "[%s] finished: %s %s", BuildUUID, cmdName, tgts)
367         os.Exit(rc)
368 }