]> Cypherpunks.ru repositories - goredo.git/blob - ifchange.go
Download link for 1.2.0 release
[goredo.git] / ifchange.go
1 /*
2 goredo -- djb's redo implementation on pure Go
3 Copyright (C) 2020-2021 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         "os"
22         "path"
23         "strings"
24 )
25
26 func collectDeps(
27         cwd, tgtOrig string,
28         level int,
29         deps map[string]map[string]struct{},
30 ) []string {
31         cwd, tgt := cwdAndTgt(path.Join(cwd, tgtOrig))
32         depPath := path.Join(cwd, RedoDir, tgt+DepSuffix)
33         fdDep, err := os.Open(depPath)
34         if err != nil {
35                 return nil
36         }
37         depInfo, err := depRead(fdDep)
38         fdDep.Close()
39         if err != nil {
40                 return nil
41         }
42         var alwayses []string
43         returnReady := false
44         tgtRel := cwdMustRel(cwd, tgt)
45         if depInfo.always {
46                 if depInfo.build == BuildUUID {
47                         trace(
48                                 CDebug, "ood: %s%s always, but already build",
49                                 strings.Repeat(". ", level), tgtOrig,
50                         )
51                         returnReady = true
52                 } else {
53                         trace(CDebug, "ood: %s%s always", strings.Repeat(". ", level), tgtOrig)
54                         alwayses = append(alwayses, tgtRel)
55                         returnReady = true
56                 }
57         }
58         for _, m := range depInfo.ifchanges {
59                 dep := m["Target"]
60                 if dep == "" {
61                         return alwayses
62                 }
63                 if dep == tgt || isSrc(cwd, dep) {
64                         continue
65                 }
66                 if !returnReady {
67                         depRel := cwdMustRel(cwd, dep)
68                         if m, ok := deps[depRel]; ok {
69                                 m[tgtRel] = struct{}{}
70                         } else {
71                                 m = make(map[string]struct{}, 0)
72                                 m[tgtRel] = struct{}{}
73                                 deps[depRel] = m
74                         }
75                         alwayses = append(alwayses, collectDeps(cwd, dep, level+1, deps)...)
76                 }
77         }
78         return alwayses
79 }
80
81 func buildDependants(tgts []string) map[string]struct{} {
82         defer Jobs.Wait()
83         trace(CDebug, "collecting deps")
84         seen := map[string]struct{}{}
85         deps := map[string]map[string]struct{}{}
86         for _, tgtInitial := range tgts {
87                 for _, tgt := range collectDeps(Cwd, tgtInitial, 0, deps) {
88                         if tgt != tgtInitial {
89                                 seen[tgt] = struct{}{}
90                         }
91                 }
92         }
93         if len(seen) == 0 {
94                 return nil
95         }
96
97         levelOrig := Level
98         defer func() {
99                 Level = levelOrig
100         }()
101         Level = 1
102         trace(CDebug, "building %d alwayses: %v", len(seen), seen)
103         errs := make(chan error, len(seen))
104         for tgt := range seen {
105                 if err := runScript(tgt, errs, false); err != nil {
106                         trace(CErr, "always run error: %s, skipping dependants", err)
107                         return nil
108                 }
109         }
110         ok := true
111         for i := 0; i < len(seen); i++ {
112                 ok = ok && isOkRun(<-errs)
113         }
114         Jobs.Wait()
115         close(errs)
116         if !ok {
117                 trace(CDebug, "alwayses failed, skipping dependants")
118                 return nil
119         }
120
121         queueSrc := make([]string, 0, len(seen))
122         for tgt := range seen {
123                 queueSrc = append(queueSrc, tgt)
124         }
125         if len(queueSrc) == 0 {
126                 return seen
127         }
128
129 RebuildDeps:
130         trace(CDebug, "checking %d dependant targets: %v", len(queueSrc), queueSrc)
131         queue := []string{}
132         for _, tgt := range queueSrc {
133                 for dep := range deps[tgt] {
134                         queue = append(queue, dep)
135                 }
136         }
137         trace(CDebug, "building %d dependant targets: %v", len(queue), queue)
138         errs = make(chan error, len(queue))
139         jobs := 0
140         queueSrc = []string{}
141         for _, tgt := range queue {
142                 ood, err := isOOD(Cwd, tgt, 0, seen)
143                 if err != nil {
144                         trace(CErr, "dependant error: %s, skipping dependants", err)
145                         return nil
146                 }
147                 if !ood {
148                         continue
149                 }
150                 if err := runScript(tgt, errs, false); err != nil {
151                         trace(CErr, "dependant error: %s, skipping dependants", err)
152                         return nil
153                 }
154                 queueSrc = append(queueSrc, tgt)
155                 seen[tgt] = struct{}{}
156                 jobs++
157         }
158         for i := 0; i < jobs; i++ {
159                 ok = ok && isOkRun(<-errs)
160         }
161         if !ok {
162                 trace(CDebug, "dependants failed, skipping them")
163                 return nil
164         }
165         Jobs.Wait()
166         close(errs)
167         if jobs == 0 {
168                 return seen
169         }
170         Level++
171         goto RebuildDeps
172 }
173
174 func ifchange(tgts []string, forced, traced bool) (bool, error) {
175         jsInit()
176         defer jsAcquire("ifchange exiting")
177         defer Jobs.Wait()
178         seen := buildDependants(tgts)
179         trace(CDebug, "building %d targets: %v", len(tgts), tgts)
180         jobs := 0
181         errs := make(chan error, len(tgts))
182         var ood bool
183         var err error
184         for _, tgt := range tgts {
185                 if _, ok := seen[tgt]; ok {
186                         trace(CDebug, "%s was already build as a dependant", tgt)
187                         continue
188                 }
189                 ood = true
190                 if !forced {
191                         ood, err = isOOD(Cwd, tgt, 0, seen)
192                         if err != nil {
193                                 return false, err
194                         }
195                 }
196                 if !ood {
197                         continue
198                 }
199                 if isSrc(Cwd, tgt) {
200                         trace(CDebug, "%s is source, not redoing", tgt)
201                         continue
202                 }
203                 if err = runScript(tgt, errs, traced); err != nil {
204                         return false, err
205                 }
206                 jobs++
207         }
208         ok := true
209         for ; jobs > 0; jobs-- {
210                 ok = ok && isOkRun(<-errs)
211         }
212         return ok, nil
213 }