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