]> Cypherpunks.ru repositories - goredo.git/blob - ifchange.go
Cache already parsed .rec during collectDeps
[goredo.git] / ifchange.go
1 /*
2 goredo -- djb's redo implementation on pure Go
3 Copyright (C) 2020-2023 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         "os"
22         "path"
23         "strings"
24 )
25
26 func collectDeps(
27         cwd, tgtOrig string,
28         level int,
29         deps map[string]map[string]struct{},
30         includeSrc bool,
31         seen map[string]struct{},
32 ) []string {
33         cwd, tgt := cwdAndTgt(path.Join(cwd, tgtOrig))
34         tgtFull := path.Join(cwd, tgt)
35         if _, ok := seen[tgtFull]; ok {
36                 return nil
37         }
38         depPath := path.Join(cwd, RedoDir, tgt+DepSuffix)
39         fdDep, err := os.Open(depPath)
40         if err != nil {
41                 return nil
42         }
43         depInfo, err := depRead(fdDep)
44         fdDep.Close()
45         if err != nil {
46                 return nil
47         }
48         seen[tgtFull] = struct{}{}
49         var alwayses []string
50         returnReady := false
51         tgtRel := cwdMustRel(cwd, tgt)
52         if depInfo.always {
53                 if depInfo.build == BuildUUID {
54                         tracef(
55                                 CDebug, "ood: %s%s always, but already build",
56                                 strings.Repeat(". ", level), tgtOrig,
57                         )
58                         returnReady = true
59                 } else {
60                         tracef(CDebug, "ood: %s%s always", strings.Repeat(". ", level), tgtOrig)
61                         alwayses = append(alwayses, tgtRel)
62                         returnReady = true
63                 }
64         }
65         for _, m := range depInfo.ifchanges {
66                 dep := m["Target"]
67                 if dep == "" {
68                         return alwayses
69                 }
70                 if dep == tgt {
71                         continue
72                 }
73                 if !includeSrc && isSrc(cwd, dep) {
74                         continue
75                 }
76                 if !returnReady {
77                         depRel := cwdMustRel(cwd, dep)
78                         if m, ok := deps[depRel]; ok {
79                                 m[tgtRel] = struct{}{}
80                         } else {
81                                 m = map[string]struct{}{}
82                                 m[tgtRel] = struct{}{}
83                                 deps[depRel] = m
84                         }
85                         alwayses = append(alwayses,
86                                 collectDeps(cwd, dep, level+1, deps, includeSrc, seen)...)
87                 }
88         }
89         return alwayses
90 }
91
92 func buildDependants(tgts []string) map[string]struct{} {
93         defer Jobs.Wait()
94         tracef(CDebug, "collecting deps")
95         seen := map[string]struct{}{}
96         deps := map[string]map[string]struct{}{}
97         collectDepsSeen := make(map[string]struct{})
98         for _, tgtInitial := range tgts {
99                 for _, tgt := range collectDeps(Cwd, tgtInitial, 0, deps, false, collectDepsSeen) {
100                         if tgt != tgtInitial {
101                                 seen[tgt] = struct{}{}
102                         }
103                 }
104         }
105         if len(seen) == 0 {
106                 return seen
107         }
108         collectDepsSeen = nil
109
110         levelOrig := Level
111         defer func() {
112                 Level = levelOrig
113         }()
114         Level = 1
115         tracef(CDebug, "building %d alwayses: %v", len(seen), seen)
116         errs := make(chan error, len(seen))
117         ok := true
118         okChecker := make(chan struct{})
119         go func() {
120                 for err := range errs {
121                         ok = isOkRun(err) && ok
122                 }
123                 close(okChecker)
124         }()
125         for tgt := range seen {
126                 if err := runScript(tgt, errs, false, false); err != nil {
127                         tracef(CErr, "always run error: %s, skipping dependants", err)
128                         Jobs.Wait()
129                         close(errs)
130                         return nil
131                 }
132         }
133         Jobs.Wait()
134         close(errs)
135         <-okChecker
136         if !ok {
137                 tracef(CDebug, "alwayses failed, skipping dependants")
138                 return nil
139         }
140
141         queueSrc := make([]string, 0, len(seen))
142         for tgt := range seen {
143                 queueSrc = append(queueSrc, tgt)
144         }
145         if len(queueSrc) == 0 {
146                 return seen
147         }
148
149 RebuildDeps:
150         tracef(CDebug, "checking %d dependant targets: %v", len(queueSrc), queueSrc)
151         queue := map[string]struct{}{}
152         for _, tgt := range queueSrc {
153                 for dep := range deps[tgt] {
154                         queue[dep] = struct{}{}
155                 }
156         }
157
158         tracef(CDebug, "building %d dependant targets: %v", len(queue), queue)
159         errs = make(chan error, len(queue))
160         okChecker = make(chan struct{})
161         jobs := 0
162         queueSrc = []string{}
163         go func() {
164                 for err := range errs {
165                         ok = isOkRun(err) && ok
166                 }
167                 close(okChecker)
168         }()
169         for tgt := range queue {
170                 ood, err := isOODWithTrace(Cwd, tgt, 0, seen)
171                 if err != nil {
172                         tracef(CErr, "dependant error: %s, skipping dependants", err)
173                         return nil
174                 }
175                 if !ood {
176                         continue
177                 }
178                 if err := runScript(tgt, errs, false, false); err != nil {
179                         tracef(CErr, "dependant error: %s, skipping dependants", err)
180                         return nil
181                 }
182                 queueSrc = append(queueSrc, tgt)
183                 seen[tgt] = struct{}{}
184                 jobs++
185         }
186         Jobs.Wait()
187         close(errs)
188         <-okChecker
189         if !ok {
190                 tracef(CDebug, "dependants failed, skipping them")
191                 return nil
192         }
193         if jobs == 0 {
194                 return seen
195         }
196         Level++
197         goto RebuildDeps
198 }
199
200 func ifchange(tgts []string, forced, traced bool) (bool, error) {
201         // only unique elements
202         m := make(map[string]struct{})
203         for _, t := range tgts {
204                 m[t] = struct{}{}
205         }
206         tgts = tgts[:0]
207         for t := range m {
208                 tgts = append(tgts, t)
209         }
210         m = nil
211
212         jsInit()
213         if !IsTopRedo {
214                 defer jsAcquire("ifchange exiting")
215         }
216         seen := buildDependants(tgts)
217         if seen == nil {
218                 Jobs.Wait()
219                 return false, nil
220         }
221         oodTgtsClear()
222         tracef(CDebug, "building %d targets: %v", len(tgts), tgts)
223         var ood bool
224         var err error
225         ok := true
226         okChecker := make(chan struct{})
227         errs := make(chan error, len(tgts))
228         go func() {
229                 for err := range errs {
230                         ok = isOkRun(err) && ok
231                 }
232                 close(okChecker)
233         }()
234         for _, tgt := range tgts {
235                 if _, ok := seen[tgt]; ok {
236                         tracef(CDebug, "%s was already build as a dependant", tgt)
237                         continue
238                 }
239                 ood = true
240                 if !forced {
241                         ood, err = isOODWithTrace(Cwd, tgt, 0, seen)
242                         if err != nil {
243                                 Jobs.Wait()
244                                 close(errs)
245                                 return false, err
246                         }
247                 }
248                 if !ood {
249                         continue
250                 }
251                 if isSrc(Cwd, tgt) {
252                         tracef(CDebug, "%s is source, not redoing", tgt)
253                         continue
254                 }
255                 if err = runScript(tgt, errs, forced, traced); err != nil {
256                         Jobs.Wait()
257                         close(errs)
258                         return false, err
259                 }
260         }
261         Jobs.Wait()
262         close(errs)
263         <-okChecker
264         return ok, nil
265 }