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