]> Cypherpunks.ru repositories - goredo.git/blob - ifchange.go
Download link for 2.6.2 release
[goredo.git] / ifchange.go
1 // goredo -- djb's redo implementation on pure Go
2 // Copyright (C) 2020-2024 Sergey Matveev <stargrave@stargrave.org>
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, version 3 of the License.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16 package main
17
18 import (
19         "strings"
20 )
21
22 func collectDeps(
23         tgt *Tgt,
24         level int,
25         deps map[string]map[string]*Tgt,
26         includeSrc bool,
27 ) []*Tgt {
28         if _, ok := DepCache[tgt.rel]; ok {
29                 return nil
30         }
31         dep, err := depRead(tgt)
32         if err != nil {
33                 return nil
34         }
35         DepCache[tgt.rel] = dep
36         var alwayses []*Tgt
37         returnReady := false
38         if dep.always {
39                 if dep.build == BuildUUID {
40                         tracef(CDebug, "ood: %s%s always, but already build",
41                                 strings.Repeat(". ", level), tgt)
42                         returnReady = true
43                 } else {
44                         tracef(CDebug, "ood: %s%s always", strings.Repeat(". ", level), tgt)
45                         alwayses = append(alwayses, tgt)
46                         returnReady = true
47                 }
48         }
49         for _, ifchange := range dep.ifchanges {
50                 if ifchange.tgt.rel == tgt.rel {
51                         continue
52                 }
53                 if !includeSrc && isSrc(ifchange.tgt) {
54                         continue
55                 }
56                 if !returnReady {
57                         if m, ok := deps[ifchange.tgt.rel]; ok {
58                                 m[tgt.rel] = tgt
59                         } else {
60                                 deps[ifchange.tgt.rel] = map[string]*Tgt{tgt.rel: tgt}
61                         }
62                         alwayses = append(alwayses,
63                                 collectDeps(ifchange.tgt, level+1, deps, includeSrc)...)
64                 }
65         }
66         return alwayses
67 }
68
69 func buildDependants(tgts []*Tgt) map[string]*Tgt {
70         defer Jobs.Wait()
71         tracef(CDebug, "collecting deps")
72         seen := make(map[string]*Tgt)
73         deps := make(map[string]map[string]*Tgt)
74         for _, tgtInitial := range tgts {
75                 for _, tgt := range collectDeps(tgtInitial, 0, deps, false) {
76                         if tgt.rel != tgtInitial.rel {
77                                 seen[tgt.rel] = tgt
78                         }
79                 }
80         }
81         TgtCache = make(map[string]*Tgt)
82         IfchangeCache = nil
83         if len(seen) == 0 {
84                 return seen
85         }
86
87         levelOrig := Level
88         defer func() { Level = levelOrig }()
89         Level = 1
90         tracef(CDebug, "building %d alwayses: %v", len(seen), seen)
91         errs := make(chan error, len(seen))
92         ok := true
93         okChecker := make(chan struct{})
94         go func() {
95                 for err := range errs {
96                         ok = isOkRun(err) && ok
97                 }
98                 close(okChecker)
99         }()
100         for _, tgt := range seen {
101                 if err := runScript(tgt, errs, false, false); err != nil {
102                         tracef(CErr, "always run error: %s, skipping dependants", err)
103                         Jobs.Wait()
104                         close(errs)
105                         return nil
106                 }
107         }
108         Jobs.Wait()
109         close(errs)
110         <-okChecker
111         if !ok {
112                 tracef(CDebug, "alwayses failed, skipping dependants")
113                 return nil
114         }
115
116         queueSrc := make([]*Tgt, 0, len(seen))
117         for _, tgt := range seen {
118                 queueSrc = append(queueSrc, tgt)
119         }
120
121 RebuildDeps:
122         tracef(CDebug, "checking %d dependant targets: %v", len(queueSrc), queueSrc)
123         queue := make(map[string]*Tgt)
124         for _, tgt := range queueSrc {
125                 for _, dep := range deps[tgt.rel] {
126                         queue[dep.rel] = dep
127                 }
128         }
129
130         tracef(CDebug, "building %d dependant targets: %v", len(queue), queue)
131         errs = make(chan error, len(queue))
132         okChecker = make(chan struct{})
133         jobs := 0
134         queueSrc = make([]*Tgt, 0)
135         go func() {
136                 for err := range errs {
137                         ok = isOkRun(err) && ok
138                 }
139                 close(okChecker)
140         }()
141         for _, tgt := range queue {
142                 ood, err := isOODWithTrace(tgt, 0, seen)
143                 if err != nil {
144                         tracef(CErr, "dependant error: %s, skipping dependants", err)
145                         return nil
146                 }
147                 if !ood {
148                         continue
149                 }
150                 if err := runScript(tgt, errs, false, false); err != nil {
151                         tracef(CErr, "dependant error: %s, skipping dependants", err)
152                         return nil
153                 }
154                 queueSrc = append(queueSrc, tgt)
155                 seen[tgt.rel] = tgt
156                 jobs++
157         }
158         Jobs.Wait()
159         close(errs)
160         <-okChecker
161         if !ok {
162                 tracef(CDebug, "dependants failed, skipping them")
163                 return nil
164         }
165         if jobs == 0 {
166                 return seen
167         }
168         Level++
169         goto RebuildDeps
170 }
171
172 func ifchange(tgts []*Tgt, forced, traced bool) (bool, error) {
173         {
174                 // only unique elements
175                 m := make(map[string]*Tgt)
176                 for _, t := range tgts {
177                         m[t.a] = t
178                 }
179                 tgts = tgts[:0]
180                 for _, t := range m {
181                         tgts = append(tgts, t)
182                 }
183         }
184
185         jsInit()
186         if !IsTopRedo {
187                 defer jsAcquire("ifchange exiting")
188         }
189         seen := buildDependants(tgts)
190         if seen == nil {
191                 Jobs.Wait()
192                 return false, nil
193         }
194         oodTgtsClear()
195         tracef(CDebug, "building %d targets: %v", len(tgts), tgts)
196         var ood bool
197         var err error
198         ok := true
199         okChecker := make(chan struct{})
200         errs := make(chan error, len(tgts))
201         go func() {
202                 for err := range errs {
203                         ok = isOkRun(err) && ok
204                 }
205                 close(okChecker)
206         }()
207         for _, tgt := range tgts {
208                 if _, ok := seen[tgt.rel]; ok {
209                         tracef(CDebug, "%s was already build as a dependant", tgt)
210                         continue
211                 }
212                 ood = true
213                 if !forced {
214                         ood, err = isOODWithTrace(tgt, 0, seen)
215                         if err != nil {
216                                 Jobs.Wait()
217                                 close(errs)
218                                 return false, ErrLine(err)
219                         }
220                 }
221                 if !ood {
222                         continue
223                 }
224                 if isSrc(tgt) {
225                         tracef(CDebug, "%s is source, not redoing", tgt)
226                         continue
227                 }
228                 if err = runScript(tgt, errs, forced, traced); err != nil {
229                         Jobs.Wait()
230                         close(errs)
231                         return false, ErrLine(err)
232                 }
233         }
234         Jobs.Wait()
235         close(errs)
236         <-okChecker
237         return ok, nil
238 }