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