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