]> Cypherpunks.ru repositories - goredo.git/blob - ood.go
Fix JS deadlock and various optimizations
[goredo.git] / ood.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 // Out-of-date determination
19
20 package main
21
22 import (
23         "errors"
24         "fmt"
25         "io"
26         "os"
27         "path"
28         "path/filepath"
29         "strings"
30
31         "go.cypherpunks.ru/recfile"
32 )
33
34 type TgtErr struct {
35         Tgt string
36         Err error
37 }
38
39 func (e TgtErr) Unwrap() error { return e.Err }
40
41 func (e TgtErr) Error() string {
42         return fmt.Sprintf("%s: %s", e.Tgt, e.Err)
43 }
44
45 func cwdAndTgt(tgt string) (string, string) {
46         cwd, tgt := path.Split(tgt)
47         cwd, err := filepath.Abs(cwd)
48         if err != nil {
49                 panic(err)
50         }
51         return cwd, tgt
52 }
53
54 func isSrc(cwd, tgt string) bool {
55         d, f := path.Split(path.Join(cwd, tgt))
56         if _, err := os.Stat(path.Join(d, f)); err != nil {
57                 return false
58         }
59         if _, err := os.Stat(path.Join(d, RedoDir, f+DepSuffix)); err == nil {
60                 return false
61         }
62         return true
63 }
64
65 func isBuiltNow(fdDep *os.File) (bool, *recfile.Reader, error) {
66         r := recfile.NewReader(fdDep)
67         m, err := r.NextMap()
68         if err != nil {
69                 return false, nil, err
70         }
71         if m["Build"] == "" {
72                 return false, r, errors.New(".dep missing Build:")
73         }
74         return m["Build"] == BuildUUID, r, nil
75 }
76
77 func rebuildStamped(cwd, tgt, depPath string) (string, error) {
78         relTgt, err := filepath.Rel(Cwd, path.Join(cwd, tgt))
79         if err != nil {
80                 panic(err)
81         }
82         errs := make(chan error, 1)
83         if err = runScript(relTgt, errs); err != nil {
84                 return "", err
85         }
86         if err = <-errs; !isOkRun(err) {
87                 return "", errors.New("build failed")
88         }
89         fdDep, err := os.Open(depPath)
90         if err != nil {
91                 return "", err
92         }
93         defer fdDep.Close()
94         builtNow, r, err := isBuiltNow(fdDep)
95         if err != nil {
96                 return "", err
97         }
98         if !builtNow {
99                 return "", errors.New("is not built")
100         }
101         var stampTheir string
102         for {
103                 m, err := r.NextMap()
104                 if err != nil {
105                         if err == io.EOF {
106                                 break
107                         }
108                         return "", err
109                 }
110                 if m["Type"] == "stamp" {
111                         stampTheir = m["Hash"]
112                         break
113                 }
114         }
115         return stampTheir, nil
116 }
117
118 func isOOD(cwd, tgt string, level int) (bool, error) {
119         tgtOrig := tgt
120         indent := strings.Repeat(". ", level)
121         trace(CDebug, "ood: %s%s checking", indent, tgtOrig)
122         cwd, tgt = cwdAndTgt(path.Join(cwd, tgt))
123         depPath := path.Join(cwd, RedoDir, tgt+DepSuffix)
124         fdDep, err := os.Open(depPath)
125         if err != nil {
126                 trace(CDebug, "ood: %s%s -> no dep: %s", indent, tgtOrig, depPath)
127                 return true, nil
128         }
129         defer fdDep.Close()
130         ood := false
131
132         builtNow, r, err := isBuiltNow(fdDep)
133         if err != nil {
134                 return true, TgtErr{tgtOrig, err}
135         }
136         if builtNow {
137                 trace(CDebug, "ood: %s%s -> already built", indent, tgtOrig)
138                 return false, nil
139         }
140
141         var stampOur string
142         ifcreates := []map[string]string{}
143         ifchanges := []map[string]string{}
144         for {
145                 m, err := r.NextMap()
146                 if err != nil {
147                         if err == io.EOF {
148                                 break
149                         }
150                         return true, TgtErr{tgtOrig, err}
151                 }
152                 switch m["Type"] {
153                 case "always":
154                         trace(CDebug, "ood: %s%s -> always", indent, tgtOrig)
155                         ood = true
156                 case "ifcreate":
157                         ifcreates = append(ifcreates, m)
158                 case "ifchange":
159                         ifchanges = append(ifchanges, m)
160                 case "stamp":
161                         stampOur = m["Hash"]
162                         trace(CDebug, "ood: %s%s -> stamped: %s", indent, tgtOrig, stampOur)
163                 default:
164                         return ood, TgtErr{tgtOrig, errors.New("invalid format of .dep")}
165                 }
166         }
167         if ood {
168                 goto StampCheck
169         }
170
171         for _, m := range ifcreates {
172                 theirTgt := m["Target"]
173                 if theirTgt == "" {
174                         return ood, TgtErr{tgtOrig, errors.New("invalid format of .dep")}
175                 }
176                 if _, err = os.Stat(path.Join(cwd, theirTgt)); err == nil {
177                         trace(CDebug, "ood: %s%s -> created", indent, tgtOrig)
178                         ood = true
179                         goto StampCheck
180                 }
181         }
182
183         for _, m := range ifchanges {
184                 dep := m["Target"]
185                 theirTs := m["Ctime"]
186                 theirHsh := m["Hash"]
187                 if dep == "" || theirTs == "" {
188                         return ood, TgtErr{tgtOrig, errors.New("invalid format of .dep")}
189                 }
190                 trace(CDebug, "ood: %s%s -> %s: checking", indent, tgtOrig, dep)
191                 fd, err := os.Open(path.Join(cwd, dep))
192                 if err != nil {
193                         if os.IsNotExist(err) {
194                                 trace(CDebug, "ood: %s%s -> %s: not exists", indent, tgtOrig, dep)
195                                 ood = true
196                                 goto StampCheck
197                         }
198                         return ood, TgtErr{tgtOrig, err}
199                 }
200                 defer fd.Close()
201                 ts, err := fileCtime(fd)
202                 if err != nil {
203                         return ood, TgtErr{tgtOrig, err}
204                 }
205                 if theirTs == ts {
206                         trace(CDebug, "ood: %s%s -> %s: same ctime", indent, tgtOrig, dep)
207                 } else if NoHash || theirHsh == "" {
208                         trace(CDebug, "ood: %s%s -> %s: ctime differs", indent, tgtOrig, dep)
209                         ood = true
210                         goto StampCheck
211                 } else {
212                         hsh, err := fileHash(fd)
213                         if err != nil {
214                                 return ood, TgtErr{tgtOrig, err}
215                         }
216                         if theirHsh != hsh {
217                                 trace(CDebug, "ood: %s%s -> %s: hash differs", indent, tgtOrig, dep)
218                                 ood = true
219                                 goto StampCheck
220                         }
221                         trace(CDebug, "ood: %s%s -> %s: same hash", indent, tgtOrig, dep)
222                 }
223                 fd.Close()
224                 if dep == tgt {
225                         trace(CDebug, "ood: %s%s -> %s: same target", indent, tgtOrig, dep)
226                         continue
227                 }
228                 if isSrc(cwd, dep) {
229                         trace(CDebug, "ood: %s%s -> %s: is source", indent, tgtOrig, dep)
230                         continue
231                 }
232                 depOod, err := isOOD(cwd, dep, level+1)
233                 if err != nil {
234                         return ood, TgtErr{tgtOrig, err}
235                 }
236                 if depOod {
237                         trace(CDebug, "ood: %s%s -> %s: ood", indent, tgtOrig, dep)
238                         ood = true
239                         goto StampCheck
240                 }
241                 trace(CDebug, "ood: %s%s -> %s: !ood", indent, tgtOrig, dep)
242         }
243
244 StampCheck:
245         if ood && stampOur != "" {
246                 trace(CDebug, "ood: %s%s run, because stamped", indent, tgtOrig)
247                 stampTheir, err := rebuildStamped(cwd, tgt, depPath)
248                 if err != nil {
249                         return true, TgtErr{tgtOrig, err}
250                 }
251                 trace(CDebug, "ood: %s%s -> stamp: %s %s", indent, tgtOrig, stampOur, stampTheir)
252                 if stampOur == stampTheir {
253                         ood = false
254                 }
255         }
256         trace(CDebug, "ood: %s%s: %v", indent, tgtOrig, ood)
257         return ood, nil
258 }