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