2 goredo -- redo implementation on pure Go
3 Copyright (C) 2020 Sergey Matveev <stargrave@stargrave.org>
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.
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.
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/>.
18 // Out-of-date determination
31 "go.cypherpunks.ru/recfile"
35 DepTypeIfcreate = "ifcreate"
36 DepTypeIfchange = "ifchange"
37 DepTypeAlways = "always"
38 DepTypeStamp = "stamp"
46 func (e TgtErr) Unwrap() error { return e.Err }
48 func (e TgtErr) Error() string {
49 return fmt.Sprintf("%s: %s", e.Tgt, e.Err)
52 func cwdMustRel(paths ...string) string {
53 rel, err := filepath.Rel(Cwd, path.Join(paths...))
60 func cwdAndTgt(tgt string) (string, string) {
61 cwd, tgt := path.Split(tgt)
62 cwd, err := filepath.Abs(cwd)
69 func isSrc(cwd, tgt string) bool {
70 d, f := path.Split(path.Join(cwd, tgt))
71 if _, err := os.Stat(path.Join(d, f)); err != nil {
74 if _, err := os.Stat(path.Join(d, RedoDir, f+DepSuffix)); err == nil {
80 func isBuiltNow(fdDep *os.File) (bool, *recfile.Reader, error) {
81 r := recfile.NewReader(fdDep)
84 return false, nil, err
87 return false, r, errors.New(".dep missing Build:")
89 return m["Build"] == BuildUUID, r, nil
92 func rebuildStamped(cwd, tgt, depPath string) (string, error) {
93 relTgt := cwdMustRel(cwd, tgt)
94 errs := make(chan error, 1)
95 if err := runScript(relTgt, errs); err != nil {
98 if err := <-errs; !isOkRun(err) {
99 return "", errors.New("build failed")
101 fdDep, err := os.Open(depPath)
106 builtNow, r, err := isBuiltNow(fdDep)
111 return "", errors.New("is not built")
113 var stampTheir string
115 m, err := r.NextMap()
122 if m["Type"] == DepTypeStamp {
123 stampTheir = m["Hash"]
127 return stampTheir, nil
130 func isOOD(cwd, tgtOrig string, level int) (bool, error) {
131 indent := strings.Repeat(". ", level)
132 trace(CDebug, "ood: %s%s checking", indent, tgtOrig)
133 cwd, tgt := cwdAndTgt(path.Join(cwd, tgtOrig))
134 depPath := path.Join(cwd, RedoDir, tgt+DepSuffix)
135 fdDep, err := os.Open(depPath)
137 trace(CDebug, "ood: %s%s -> no dep: %s", indent, tgtOrig, depPath)
143 builtNow, r, err := isBuiltNow(fdDep)
145 return true, TgtErr{tgtOrig, err}
148 trace(CDebug, "ood: %s%s -> already built", indent, tgtOrig)
153 ifcreates := []map[string]string{}
154 ifchanges := []map[string]string{}
156 m, err := r.NextMap()
161 return true, TgtErr{tgtOrig, err}
165 trace(CDebug, "ood: %s%s -> always", indent, tgtOrig)
167 case DepTypeIfcreate:
168 ifcreates = append(ifcreates, m)
169 case DepTypeIfchange:
170 ifchanges = append(ifchanges, m)
173 trace(CDebug, "ood: %s%s -> stamped: %s", indent, tgtOrig, stampOur)
175 return ood, TgtErr{tgtOrig, errors.New("invalid format of .dep")}
182 for _, m := range ifcreates {
183 theirTgt := m["Target"]
185 return ood, TgtErr{tgtOrig, errors.New("invalid format of .dep")}
187 if _, err = os.Stat(path.Join(cwd, theirTgt)); err == nil {
188 trace(CDebug, "ood: %s%s -> created", indent, tgtOrig)
194 for _, m := range ifchanges {
196 theirTs := m["Ctime"]
197 theirHsh := m["Hash"]
198 if dep == "" || theirTs == "" {
199 return ood, TgtErr{tgtOrig, errors.New("invalid format of .dep")}
201 trace(CDebug, "ood: %s%s -> %s: checking", indent, tgtOrig, dep)
202 fd, err := os.Open(path.Join(cwd, dep))
204 if os.IsNotExist(err) {
205 trace(CDebug, "ood: %s%s -> %s: not exists", indent, tgtOrig, dep)
209 return ood, TgtErr{tgtOrig, err}
212 ts, err := fileCtime(fd)
214 return ood, TgtErr{tgtOrig, err}
217 trace(CDebug, "ood: %s%s -> %s: same ctime", indent, tgtOrig, dep)
218 } else if NoHash || theirHsh == "" {
219 trace(CDebug, "ood: %s%s -> %s: ctime differs", indent, tgtOrig, dep)
223 hsh, err := fileHash(fd)
225 return ood, TgtErr{tgtOrig, err}
228 trace(CDebug, "ood: %s%s -> %s: hash differs", indent, tgtOrig, dep)
232 trace(CDebug, "ood: %s%s -> %s: same hash", indent, tgtOrig, dep)
236 trace(CDebug, "ood: %s%s -> %s: same target", indent, tgtOrig, dep)
240 trace(CDebug, "ood: %s%s -> %s: is source", indent, tgtOrig, dep)
243 depOod, err := isOOD(cwd, dep, level+1)
245 return ood, TgtErr{tgtOrig, err}
248 trace(CDebug, "ood: %s%s -> %s: ood", indent, tgtOrig, dep)
252 trace(CDebug, "ood: %s%s -> %s: !ood", indent, tgtOrig, dep)
256 if ood && stampOur != "" {
257 trace(CDebug, "ood: %s%s run, because stamped", indent, tgtOrig)
258 stampTheir, err := rebuildStamped(cwd, tgt, depPath)
260 return true, TgtErr{tgtOrig, err}
262 trace(CDebug, "ood: %s%s -> stamp: %s %s", indent, tgtOrig, stampOur, stampTheir)
263 if stampOur == stampTheir {
267 trace(CDebug, "ood: %s%s: %v", indent, tgtOrig, ood)