]> Cypherpunks.ru repositories - goredo.git/blob - ifchange.go
Removed hashless mode, small bugfixes, tai64nlocal
[goredo.git] / ifchange.go
1 /*
2 goredo -- redo implementation on pure Go
3 Copyright (C) 2020 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 _, tgt := range tgts {
87                 for _, tgt := range collectDeps(Cwd, tgt, 0, deps) {
88                         seen[tgt] = struct{}{}
89                 }
90         }
91         if len(seen) == 0 {
92                 return nil
93         }
94
95         trace(CDebug, "building %d alwayses: %v", len(seen), seen)
96         errs := make(chan error, len(seen))
97         for tgt, _ := range seen {
98                 if err := runScript(tgt, errs); err != nil {
99                         trace(CErr, "always run error: %s, skipping dependants", err)
100                         return nil
101                 }
102         }
103         ok := true
104         for i := 0; i < len(seen); i++ {
105                 ok = ok && isOkRun(<-errs)
106         }
107         Jobs.Wait()
108         close(errs)
109         if !ok {
110                 trace(CDebug, "alwayses failed, skipping depdendants")
111                 return nil
112         }
113
114         queueSrc := make([]string, 0, len(seen))
115         for tgt, _ := range seen {
116                 queueSrc = append(queueSrc, tgt)
117         }
118         if len(queueSrc) == 0 {
119                 return seen
120         }
121         levelOrig := Level
122         defer func() {
123                 Level = levelOrig
124         }()
125         Level = 1
126
127 RebuildDeps:
128         trace(CDebug, "checking %d dependant targets: %v", len(queueSrc), queueSrc)
129         queue := []string{}
130         for _, tgt := range queueSrc {
131                 for dep, _ := range deps[tgt] {
132                         queue = append(queue, dep)
133                 }
134         }
135         trace(CDebug, "building %d dependant targets: %v", len(queue), queue)
136         errs = make(chan error, len(queue))
137         jobs := 0
138         queueSrc = []string{}
139         for _, tgt := range queue {
140                 ood, err := isOOD(Cwd, tgt, 0, seen)
141                 if err != nil {
142                         trace(CErr, "dependant error: %s, skipping dependants", err)
143                         return nil
144                 }
145                 if !ood {
146                         continue
147                 }
148                 if err := runScript(tgt, errs); err != nil {
149                         trace(CErr, "dependant error: %s, skipping dependants", err)
150                         return nil
151                 }
152                 queueSrc = append(queueSrc, tgt)
153                 seen[tgt] = struct{}{}
154                 jobs++
155         }
156         for i := 0; i < jobs; i++ {
157                 ok = ok && isOkRun(<-errs)
158         }
159         if !ok {
160                 trace(CDebug, "dependants failed, skipping them")
161                 return nil
162         }
163         Jobs.Wait()
164         close(errs)
165         if jobs == 0 {
166                 return seen
167         }
168         Level++
169         goto RebuildDeps
170 }
171
172 func ifchange(tgts []string, forced bool) (bool, error) {
173         jsInit()
174         defer jsAcquire("ifchange exiting")
175         defer Jobs.Wait()
176         seen := buildDependants(tgts)
177         trace(CDebug, "building %d targets: %v", len(tgts), tgts)
178         jobs := 0
179         errs := make(chan error, len(tgts))
180         var ood bool
181         var err error
182         for _, tgt := range tgts {
183                 if _, ok := seen[tgt]; ok {
184                         trace(CDebug, "%s was already build as a dependenant", tgt)
185                         continue
186                 }
187                 ood = true
188                 if !forced {
189                         ood, err = isOOD(Cwd, tgt, 0, seen)
190                         if err != nil {
191                                 return false, err
192                         }
193                 }
194                 if !ood {
195                         continue
196                 }
197                 if isSrc(Cwd, tgt) {
198                         trace(CDebug, "%s is source, not redoing", tgt)
199                         continue
200                 }
201                 if err = runScript(tgt, errs); err != nil {
202                         return false, err
203                 }
204                 jobs++
205         }
206         ok := true
207         for ; jobs > 0; jobs-- {
208                 ok = ok && isOkRun(<-errs)
209         }
210         return ok, nil
211 }