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