]> Cypherpunks.ru repositories - goredo.git/blob - main.go
Fix JS deadlock and various optimizations
[goredo.git] / main.go
1 /*
2 goredo -- redo implementation on pure Go
3 Copyright (C) 2020 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         "crypto/rand"
22         "flag"
23         "fmt"
24         "io"
25         "io/ioutil"
26         "log"
27         "os"
28         "path"
29         "path/filepath"
30         "strconv"
31         "sync"
32
33         "go.cypherpunks.ru/recfile"
34         "golang.org/x/sys/unix"
35 )
36
37 const (
38         RedoLevelEnv        = "REDO_LEVEL"
39         RedoDepFdEnv        = "REDO_DEP_FD"
40         RedoDirPrefixEnv    = "REDO_DIRPREFIX"
41         RedoBuildUUIDEnv    = "REDO_BUILD_UUID"
42         RedoStderrPrefixEnv = "REDO_STDERR_PREFIX"
43         RedoJSRFdEnv        = "REDO_JSR_FD"
44         RedoJSWFdEnv        = "REDO_JSW_FD"
45 )
46
47 var (
48         Level       = 0
49         Trace  bool = false
50         Force  bool = false
51         NoHash bool = false
52         NoSync bool = false
53         Cwd    string
54         Jobs   sync.WaitGroup
55
56         JobsN = flag.Uint("j", 1, "number of parallel jobs (0 for infinite)")
57
58         StderrKeep   bool = false
59         StderrSilent bool = false
60         StderrPrefix string
61
62         BuildUUID string
63         IsMaster  bool = false
64 )
65
66 func mustSetenv(key, value string) {
67         if err := os.Setenv(key, value); 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 main() {
85         xflag := flag.Bool("x", false, "trace current target (sh -x) (set REDO_TRACE=1 for others too)")
86         stderrKeep := flag.Bool("stderr-keep", false, "keep job's stderr (REDO_STDERR_KEEP=1)")
87         stderrSilent := flag.Bool("stderr-silent", false, "do not print job's stderr (REDO_STDERR_SILENT=1)")
88         debug := flag.Bool("debug", false, "enable debug logging (REDO_DEBUG=1)")
89         logWait := flag.Bool("log-wait", false, "enable wait messages logging (REDO_LOG_WAIT=1)")
90         logLock := flag.Bool("log-lock", false, "enable lock messages logging (REDO_LOG_LOCK=1)")
91         logPid := flag.Bool("log-pid", false, "append PIDs (REDO_LOG_PID=1)")
92         logJS := flag.Bool("log-js", false, "enable jobserver messages logging (REDO_LOG_JS=1)")
93         version := flag.Bool("version", false, "print version")
94         warranty := flag.Bool("warranty", false, "print warranty information")
95         symlinks := flag.Bool("symlinks", false, "create necessary symlinks in current direcotyr")
96
97         flag.Usage = usage
98         flag.Parse()
99         if *warranty {
100                 fmt.Println(Warranty)
101                 return
102         }
103         if *version {
104                 fmt.Println(versionGet())
105                 return
106         }
107         if *symlinks {
108                 for _, cmdName := range []string{
109                         "redo",
110                         "redo-always",
111                         "redo-cleanup",
112                         "redo-ifchange",
113                         "redo-ifcreate",
114                         "redo-log",
115                         "redo-stamp",
116                         "redo-whichdo",
117                 } {
118                         if err := os.Symlink(os.Args[0], cmdName); err != nil {
119                                 log.Fatalln(err)
120                         }
121                 }
122                 return
123         }
124         log.SetFlags(0)
125
126         var err error
127         Cwd, err = unix.Getwd()
128         if err != nil {
129                 panic(err)
130         }
131
132         NoColor = os.Getenv("NO_COLOR") != ""
133         NoHash = os.Getenv("REDO_NO_HASH") == "1"
134         NoSync = os.Getenv("REDO_NO_SYNC") == "1"
135
136         TopDir = os.Getenv("REDO_TOP_DIR")
137         if TopDir != "" {
138                 TopDir, err = filepath.Abs(TopDir)
139                 if err != nil {
140                         panic(err)
141                 }
142         }
143
144         if *stderrKeep {
145                 mustSetenv("REDO_STDERR_KEEP", "1")
146         }
147         if *stderrSilent {
148                 mustSetenv("REDO_STDERR_SILENT", "1")
149         }
150         if *debug {
151                 mustSetenv("REDO_DEBUG", "1")
152         }
153         if *logWait {
154                 mustSetenv("REDO_LOG_WAIT", "1")
155         }
156         if *logLock {
157                 mustSetenv("REDO_LOG_LOCK", "1")
158         }
159         if *logPid {
160                 mustSetenv("REDO_LOG_PID", "1")
161         }
162         if *logJS {
163                 mustSetenv("REDO_LOG_JS", "1")
164         }
165         StderrKeep = os.Getenv("REDO_STDERR_KEEP") == "1"
166         StderrSilent = os.Getenv("REDO_STDERR_SILENT") == "1"
167         Debug = os.Getenv("REDO_DEBUG") == "1"
168         LogWait = os.Getenv("REDO_LOG_WAIT") == "1"
169         LogLock = os.Getenv("REDO_LOG_LOCK") == "1"
170         LogJS = os.Getenv("REDO_LOG_JS") == "1"
171         if Debug || os.Getenv("REDO_LOG_PID") == "1" {
172                 MyPid = os.Getpid()
173         }
174         if *xflag {
175                 Trace = true
176         } else {
177                 Trace = os.Getenv("REDO_TRACE") == "1"
178         }
179
180         // Those are internal envs
181         StderrPrefix = os.Getenv(RedoStderrPrefixEnv)
182         if v := os.Getenv(RedoLevelEnv); v != "" {
183                 level, err := strconv.ParseUint(v, 10, 64)
184                 if err != nil {
185                         panic(err)
186                 }
187                 Level = int(level)
188         }
189         var fdDep *os.File
190         if v := os.Getenv(RedoDepFdEnv); v != "" {
191                 fdDep = mustParseFd(v, RedoDepFdEnv)
192         }
193         BuildUUID = os.Getenv(RedoBuildUUIDEnv)
194         if BuildUUID == "" {
195                 IsMaster = true
196                 raw := new([16]byte)
197                 if _, err = io.ReadFull(rand.Reader, raw[:]); err != nil {
198                         panic(err)
199                 }
200                 raw[6] = (raw[6] & 0x0F) | uint8(4<<4) // version 4
201                 BuildUUID = fmt.Sprintf(
202                         "%x-%x-%x-%x-%x",
203                         raw[0:4], raw[4:6], raw[6:8], raw[8:10], raw[10:],
204                 )
205         }
206
207         tgts := flag.Args()
208         if len(tgts) == 0 {
209                 tgts = []string{"all"}
210         }
211         ok := true
212         err = nil
213         cmdName := path.Base(os.Args[0])
214         trace(CDebug, "[%s] run: %s %s [%s]", BuildUUID, cmdName, tgts, Cwd)
215
216 CmdSwitch:
217         switch cmdName {
218         case "redo":
219                 Force = true
220                 ok, err = ifchange(tgts)
221         case "redo-ifchange":
222                 ok, err = ifchange(tgts)
223                 writeDeps(fdDep, tgts)
224         case "redo-ifcreate":
225                 if fdDep == nil {
226                         log.Fatalln("no", RedoDepFdEnv)
227                 }
228                 for _, tgt := range tgts {
229                         err = ifcreate(fdDep, tgt)
230                         if err != nil {
231                                 break
232                         }
233                 }
234         case "redo-always":
235                 if fdDep == nil {
236                         log.Fatalln("no", RedoDepFdEnv)
237                 }
238                 err = always(fdDep)
239         case "redo-cleanup":
240                 for _, what := range tgts {
241                         err = cleanupWalker(Cwd, what)
242                         if err != nil {
243                                 break
244                         }
245                 }
246         case "redo-stamp":
247                 if fdDep == nil {
248                         log.Fatalln("no", RedoDepFdEnv)
249                 }
250                 err = stamp(fdDep, os.Stdin)
251         case "redo-log":
252                 if len(tgts) != 1 {
253                         log.Fatalln("single target expected")
254                 }
255                 d, t := cwdAndTgt(tgts[0])
256                 var fd *os.File
257                 fd, err = os.Open(path.Join(d, RedoDir, t+LogSuffix))
258                 if err != nil {
259                         break
260                 }
261                 _, err = io.Copy(os.Stdout, fd)
262         case "redo-whichdo":
263                 if len(tgts) != 1 {
264                         log.Fatalln("single target expected")
265                 }
266                 var fdTmp *os.File
267                 fdTmp, err = ioutil.TempFile("", "")
268                 if err != nil {
269                         break
270                 }
271                 os.Remove(fdTmp.Name())
272                 var doFile string
273                 cwd, tgt := cwdAndTgt(tgts[0])
274                 doFile, _, err = findDo(fdTmp, cwd, tgt)
275                 if err != nil {
276                         break
277                 }
278                 fdTmp.Seek(0, 0)
279                 r := recfile.NewReader(fdTmp)
280                 for {
281                         m, err := r.NextMap()
282                         if err != nil {
283                                 if err == io.EOF {
284                                         break
285                                 }
286                                 break CmdSwitch
287                         }
288                         rel, err := filepath.Rel(Cwd, path.Join(cwd, m["Target"]))
289                         if err != nil {
290                                 panic(err)
291                         }
292                         fmt.Println(rel)
293                 }
294                 ok = doFile != ""
295         default:
296                 log.Fatalln("unknown command", cmdName)
297         }
298         if err != nil {
299                 log.Println(err)
300         }
301         rc := 0
302         if !ok || err != nil {
303                 rc = 1
304         }
305         trace(CDebug, "[%s] finished: %s %s", BuildUUID, cmdName, tgts)
306         os.Exit(rc)
307 }