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